|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00028 require_once($CFG->dirroot . '/question/type/shortanswer/renderer.php'); 00029 00030 00041 class qtype_multianswer_renderer extends qtype_renderer { 00042 00043 public function formulation_and_controls(question_attempt $qa, 00044 question_display_options $options) { 00045 $question = $qa->get_question(); 00046 00047 $output = ''; 00048 foreach ($question->textfragments as $i => $fragment) { 00049 if ($i > 0) { 00050 $index = $question->places[$i]; 00051 $output .= $this->subquestion($qa, $options, $index, 00052 $question->subquestions[$index]); 00053 } 00054 $output .= $question->format_text($fragment, $question->questiontextformat, 00055 $qa, 'question', 'questiontext', $question->id); 00056 } 00057 00058 $this->page->requires->js_init_call('M.qtype_multianswer.init', 00059 array('#q' . $qa->get_slot()), false, array( 00060 'name' => 'qtype_multianswer', 00061 'fullpath' => '/question/type/multianswer/module.js', 00062 'requires' => array('base', 'node', 'event', 'overlay'), 00063 )); 00064 00065 return $output; 00066 } 00067 00068 public function subquestion(question_attempt $qa, 00069 question_display_options $options, $index, question_graded_automatically $subq) { 00070 00071 $subtype = $subq->qtype->name(); 00072 if ($subtype == 'numerical' || $subtype == 'shortanswer') { 00073 $subrenderer = 'textfield'; 00074 } else if ($subtype == 'multichoice') { 00075 if ($subq->layout == qtype_multichoice_base::LAYOUT_DROPDOWN) { 00076 $subrenderer = 'multichoice_inline'; 00077 } else if ($subq->layout == qtype_multichoice_base::LAYOUT_HORIZONTAL) { 00078 $subrenderer = 'multichoice_horizontal'; 00079 } else { 00080 $subrenderer = 'multichoice_vertical'; 00081 } 00082 } else { 00083 throw new coding_exception('Unexpected subquestion type.', $subq); 00084 } 00085 $renderer = $this->page->get_renderer('qtype_multianswer', $subrenderer); 00086 return $renderer->subquestion($qa, $options, $index, $subq); 00087 } 00088 00089 public function correct_response(question_attempt $qa) { 00090 return ''; 00091 } 00092 } 00093 00094 00102 abstract class qtype_multianswer_subq_renderer_base extends qtype_renderer { 00103 00104 abstract public function subquestion(question_attempt $qa, 00105 question_display_options $options, $index, 00106 question_graded_automatically $subq); 00107 00118 protected function feedback_popup(question_graded_automatically $subq, 00119 $fraction, $feedbacktext, $rightanswer, question_display_options $options) { 00120 00121 if (!$options->feedback) { 00122 return ''; 00123 } 00124 00125 $feedback = array(); 00126 if ($options->correctness) { 00127 if (is_null($fraction)) { 00128 $state = question_state::$gaveup; 00129 } else { 00130 $state = question_state::graded_state_for_fraction($fraction); 00131 } 00132 $feedback[] = $state->default_string(true); 00133 } 00134 00135 if ($feedbacktext) { // Note $options->feedback is already checked above. 00136 $feedback[] = $feedbacktext; 00137 } 00138 00139 if ($options->rightanswer) { 00140 $feedback[] = get_string('correctansweris', 'qtype_shortanswer', $rightanswer); 00141 } 00142 00143 $subfraction = ''; 00144 if ($options->marks >= question_display_options::MARK_AND_MAX && $subq->maxmark > 0) { 00145 $a = new stdClass(); 00146 $a->mark = format_float($fraction * $subq->maxmark, $options->markdp); 00147 $a->max = format_float($subq->maxmark, $options->markdp); 00148 $feedback[] = get_string('markoutofmax', 'question', $a); 00149 } 00150 00151 if (!$feedback) { 00152 return ''; 00153 } 00154 00155 return html_writer::tag('span', implode('<br />', $feedback), 00156 array('class' => 'feedbackspan accesshide')); 00157 } 00158 } 00159 00160 00168 class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_renderer_base { 00169 00170 public function subquestion(question_attempt $qa, question_display_options $options, 00171 $index, question_graded_automatically $subq) { 00172 00173 $fieldprefix = 'sub' . $index . '_'; 00174 $fieldname = $fieldprefix . 'answer'; 00175 00176 $response = $qa->get_last_qt_var($fieldname); 00177 if ($subq->qtype->name() == 'shortanswer') { 00178 $matchinganswer = $subq->get_matching_answer(array('answer' => $response)); 00179 } else if ($subq->qtype->name() == 'numerical') { 00180 $matchinganswer = $subq->get_matching_answer($response, 1); 00181 } else { 00182 $matchinganswer = $subq->get_matching_answer($response); 00183 } 00184 00185 if (!$matchinganswer) { 00186 $matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML); 00187 } 00188 00189 // Work out a good input field size. 00190 $size = max(1, strlen(trim($response)) + 1); 00191 foreach ($subq->answers as $ans) { 00192 $size = max($size, strlen(trim($ans->answer))); 00193 } 00194 $size = min(60, round($size + rand(0, $size*0.15))); 00195 // The rand bit is to make guessing harder 00196 00197 $inputattributes = array( 00198 'type' => 'text', 00199 'name' => $qa->get_qt_field_name($fieldname), 00200 'value' => $response, 00201 'id' => $qa->get_qt_field_name($fieldname), 00202 'size' => $size, 00203 ); 00204 if ($options->readonly) { 00205 $inputattributes['readonly'] = 'readonly'; 00206 } 00207 00208 $feedbackimg = ''; 00209 if ($options->correctness) { 00210 $inputattributes['class'] = $this->feedback_class($matchinganswer->fraction); 00211 $feedbackimg = $this->feedback_image($matchinganswer->fraction); 00212 } 00213 00214 if ($subq->qtype->name() == 'shortanswer') { 00215 $correctanswer = $subq->get_matching_answer($subq->get_correct_response()); 00216 } else { 00217 $correctanswer = $subq->get_correct_answer(); 00218 } 00219 00220 $feedbackpopup = $this->feedback_popup($subq, $matchinganswer->fraction, 00221 $subq->format_text($matchinganswer->feedback, $matchinganswer->feedbackformat, 00222 $qa, 'question', 'answerfeedback', $matchinganswer->id), 00223 s($correctanswer->answer), $options); 00224 00225 $output = ''; 00226 $output .= html_writer::start_tag('label', array('class' => 'subq')); 00227 $output .= html_writer::empty_tag('input', $inputattributes); 00228 $output .= $feedbackimg; 00229 $output .= $feedbackpopup; 00230 $output .= html_writer::end_tag('label'); 00231 00232 return $output; 00233 } 00234 } 00235 00236 00243 class qtype_multianswer_multichoice_inline_renderer 00244 extends qtype_multianswer_subq_renderer_base { 00245 00246 public function subquestion(question_attempt $qa, question_display_options $options, 00247 $index, question_graded_automatically $subq) { 00248 00249 $fieldprefix = 'sub' . $index . '_'; 00250 $fieldname = $fieldprefix . 'answer'; 00251 00252 $response = $qa->get_last_qt_var($fieldname); 00253 $choices = array(); 00254 $matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML); 00255 $rightanswer = null; 00256 foreach ($subq->get_order($qa) as $value => $ansid) { 00257 $ans = $subq->answers[$ansid]; 00258 $choices[$value] = $subq->format_text($ans->answer, $ans->answerformat, 00259 $qa, 'question', 'answer', $ansid); 00260 if ($subq->is_choice_selected($response, $value)) { 00261 $matchinganswer = $ans; 00262 } 00263 } 00264 00265 $inputattributes = array( 00266 'id' => $qa->get_qt_field_name($fieldname), 00267 ); 00268 if ($options->readonly) { 00269 $inputattributes['disabled'] = 'disabled'; 00270 } 00271 00272 $feedbackimg = ''; 00273 if ($options->correctness) { 00274 $inputattributes['class'] = $this->feedback_class($matchinganswer->fraction); 00275 $feedbackimg = $this->feedback_image($matchinganswer->fraction); 00276 } 00277 00278 $select = html_writer::select($choices, $qa->get_qt_field_name($fieldname), 00279 $response, array('' => ''), $inputattributes); 00280 00281 $order = $subq->get_order($qa); 00282 $rightanswer = $subq->answers[$order[reset($subq->get_correct_response())]]; 00283 $feedbackpopup = $this->feedback_popup($subq, $matchinganswer->fraction, 00284 $subq->format_text($matchinganswer->feedback, $matchinganswer->feedbackformat, 00285 $qa, 'question', 'answerfeedback', $matchinganswer->id), 00286 $subq->format_text($rightanswer->answer, $rightanswer->answerformat, 00287 $qa, 'question', 'answer', $rightanswer->id), $options); 00288 00289 $output = ''; 00290 $output .= html_writer::start_tag('label', array('class' => 'subq')); 00291 $output .= $select; 00292 $output .= $feedbackimg; 00293 $output .= $feedbackpopup; 00294 $output .= html_writer::end_tag('label'); 00295 00296 return $output; 00297 } 00298 } 00299 00300 00308 class qtype_multianswer_multichoice_vertical_renderer extends qtype_multianswer_subq_renderer_base { 00309 00310 public function subquestion(question_attempt $qa, question_display_options $options, 00311 $index, question_graded_automatically $subq) { 00312 00313 $fieldprefix = 'sub' . $index . '_'; 00314 $fieldname = $fieldprefix . 'answer'; 00315 $response = $qa->get_last_qt_var($fieldname); 00316 00317 $inputattributes = array( 00318 'type' => 'radio', 00319 'name' => $qa->get_qt_field_name($fieldname), 00320 ); 00321 if ($options->readonly) { 00322 $inputattributes['disabled'] = 'disabled'; 00323 } 00324 00325 $result = $this->all_choices_wrapper_start(); 00326 $fraction = null; 00327 foreach ($subq->get_order($qa) as $value => $ansid) { 00328 $ans = $subq->answers[$ansid]; 00329 00330 $inputattributes['value'] = $value; 00331 $inputattributes['id'] = $inputattributes['name'] . $value; 00332 00333 $isselected = $subq->is_choice_selected($response, $value); 00334 if ($isselected) { 00335 $inputattributes['checked'] = 'checked'; 00336 $fraction = $ans->fraction; 00337 } else { 00338 unset($inputattributes['checked']); 00339 } 00340 00341 $class = 'r' . ($value % 2); 00342 if ($options->correctness && $isselected) { 00343 $feedbackimg = $this->feedback_image($ans->fraction); 00344 $class .= ' ' . $this->feedback_class($ans->fraction); 00345 } else { 00346 $feedbackimg = ''; 00347 } 00348 00349 $result .= $this->choice_wrapper_start($class); 00350 $result .= html_writer::empty_tag('input', $inputattributes); 00351 $result .= html_writer::tag('label', $subq->format_text($ans->answer, 00352 $ans->answerformat, $qa, 'question', 'answer', $ansid), 00353 array('for' => $inputattributes['id'])); 00354 $result .= $feedbackimg; 00355 00356 if ($options->feedback && $isselected && trim($ans->feedback)) { 00357 $result .= html_writer::tag('div', 00358 $subq->format_text($ans->feedback, $ans->feedbackformat, 00359 $qa, 'question', 'answerfeedback', $ansid), 00360 array('class' => 'specificfeedback')); 00361 } 00362 00363 $result .= $this->choice_wrapper_end(); 00364 } 00365 00366 $result .= $this->all_choices_wrapper_end(); 00367 00368 if ($options->feedback && $options->marks >= question_display_options::MARK_AND_MAX && 00369 $subq->maxmark > 0) { 00370 $a = new stdClass(); 00371 $a->mark = format_float($fraction * $subq->maxmark, $options->markdp); 00372 $a->max = format_float($subq->maxmark, $options->markdp); 00373 00374 $result .= html_writer::tag('div', get_string('markoutofmax', 'question', $a), 00375 array('class' => 'outcome')); 00376 } 00377 00378 return $result; 00379 } 00380 00385 protected function choice_wrapper_start($class) { 00386 return html_writer::start_tag('div', array('class' => $class)); 00387 } 00388 00392 protected function choice_wrapper_end() { 00393 return html_writer::end_tag('div'); 00394 } 00395 00399 protected function all_choices_wrapper_start() { 00400 return html_writer::start_tag('div', array('class' => 'answer')); 00401 } 00402 00406 protected function all_choices_wrapper_end() { 00407 return html_writer::end_tag('div'); 00408 } 00409 } 00410 00411 00419 class qtype_multianswer_multichoice_horizontal_renderer 00420 extends qtype_multianswer_multichoice_vertical_renderer { 00421 00422 protected function choice_wrapper_start($class) { 00423 return html_writer::start_tag('td', array('class' => $class)); 00424 } 00425 00426 protected function choice_wrapper_end() { 00427 return html_writer::end_tag('td'); 00428 } 00429 00430 protected function all_choices_wrapper_start() { 00431 return html_writer::start_tag('table', array('class' => 'answer')) . 00432 html_writer::start_tag('tbody') . html_writer::start_tag('tr'); 00433 } 00434 00435 protected function all_choices_wrapper_end() { 00436 return html_writer::end_tag('tr') . html_writer::end_tag('tbody') . 00437 html_writer::end_tag('table'); 00438 } 00439 }