[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Multianswer question renderer classes. 19 * Handle shortanswer, numerical and various multichoice subquestions 20 * 21 * @package qtype 22 * @subpackage multianswer 23 * @copyright 2010 Pierre Pichet 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27 28 require_once($CFG->dirroot . '/question/type/shortanswer/renderer.php'); 29 30 31 /** 32 * Base class for generating the bits of output common to multianswer 33 * (Cloze) questions. 34 * This render the main question text and transfer to the subquestions 35 * the task of display their input elements and status 36 * feedback, grade, correct answer(s) 37 * 38 * @copyright 2010 Pierre Pichet 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class qtype_multianswer_renderer extends qtype_renderer { 42 43 public function formulation_and_controls(question_attempt $qa, 44 question_display_options $options) { 45 $question = $qa->get_question(); 46 47 $output = ''; 48 foreach ($question->textfragments as $i => $fragment) { 49 if ($i > 0) { 50 $index = $question->places[$i]; 51 $output .= $this->subquestion($qa, $options, $index, 52 $question->subquestions[$index]); 53 } 54 $output .= $question->format_text($fragment, $question->questiontextformat, 55 $qa, 'question', 'questiontext', $question->id); 56 } 57 58 $this->page->requires->js_init_call('M.qtype_multianswer.init', 59 array('#q' . $qa->get_slot()), false, array( 60 'name' => 'qtype_multianswer', 61 'fullpath' => '/question/type/multianswer/module.js', 62 'requires' => array('base', 'node', 'event', 'overlay'), 63 )); 64 65 return $output; 66 } 67 68 public function subquestion(question_attempt $qa, 69 question_display_options $options, $index, question_graded_automatically $subq) { 70 71 $subtype = $subq->qtype->name(); 72 if ($subtype == 'numerical' || $subtype == 'shortanswer') { 73 $subrenderer = 'textfield'; 74 } else if ($subtype == 'multichoice') { 75 if ($subq->layout == qtype_multichoice_base::LAYOUT_DROPDOWN) { 76 $subrenderer = 'multichoice_inline'; 77 } else if ($subq->layout == qtype_multichoice_base::LAYOUT_HORIZONTAL) { 78 $subrenderer = 'multichoice_horizontal'; 79 } else { 80 $subrenderer = 'multichoice_vertical'; 81 } 82 } else { 83 throw new coding_exception('Unexpected subquestion type.', $subq); 84 } 85 $renderer = $this->page->get_renderer('qtype_multianswer', $subrenderer); 86 return $renderer->subquestion($qa, $options, $index, $subq); 87 } 88 89 public function correct_response(question_attempt $qa) { 90 return ''; 91 } 92 } 93 94 95 /** 96 * Subclass for generating the bits of output specific to shortanswer 97 * subquestions. 98 * 99 * @copyright 2011 The Open University 100 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 101 */ 102 abstract class qtype_multianswer_subq_renderer_base extends qtype_renderer { 103 104 abstract public function subquestion(question_attempt $qa, 105 question_display_options $options, $index, 106 question_graded_automatically $subq); 107 108 /** 109 * Render the feedback pop-up contents. 110 * 111 * @param question_graded_automatically $subq the subquestion. 112 * @param float $fraction the mark the student got. null if this subq was not answered. 113 * @param string $feedbacktext the feedback text, already processed with format_text etc. 114 * @param string $rightanswer the right answer, already processed with format_text etc. 115 * @param question_display_options $options the display options. 116 * @return string the HTML for the feedback popup. 117 */ 118 protected function feedback_popup(question_graded_automatically $subq, 119 $fraction, $feedbacktext, $rightanswer, question_display_options $options) { 120 121 $feedback = array(); 122 if ($options->correctness) { 123 if (is_null($fraction)) { 124 $state = question_state::$gaveup; 125 } else { 126 $state = question_state::graded_state_for_fraction($fraction); 127 } 128 $feedback[] = $state->default_string(true); 129 } 130 131 if ($options->feedback && $feedbacktext) { 132 $feedback[] = $feedbacktext; 133 } 134 135 if ($options->rightanswer) { 136 $feedback[] = get_string('correctansweris', 'qtype_shortanswer', $rightanswer); 137 } 138 139 $subfraction = ''; 140 if ($options->marks >= question_display_options::MARK_AND_MAX && $subq->maxmark > 0 141 && (!is_null($fraction) || $feedback)) { 142 $a = new stdClass(); 143 $a->mark = format_float($fraction * $subq->maxmark, $options->markdp); 144 $a->max = format_float($subq->maxmark, $options->markdp); 145 $feedback[] = get_string('markoutofmax', 'question', $a); 146 } 147 148 if (!$feedback) { 149 return ''; 150 } 151 152 return html_writer::tag('span', implode('<br />', $feedback), 153 array('class' => 'feedbackspan accesshide')); 154 } 155 } 156 157 158 /** 159 * Subclass for generating the bits of output specific to shortanswer 160 * subquestions. 161 * 162 * @copyright 2011 The Open University 163 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 164 */ 165 class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_renderer_base { 166 167 public function subquestion(question_attempt $qa, question_display_options $options, 168 $index, question_graded_automatically $subq) { 169 170 $fieldprefix = 'sub' . $index . '_'; 171 $fieldname = $fieldprefix . 'answer'; 172 173 $response = $qa->get_last_qt_var($fieldname); 174 if ($subq->qtype->name() == 'shortanswer') { 175 $matchinganswer = $subq->get_matching_answer(array('answer' => $response)); 176 } else if ($subq->qtype->name() == 'numerical') { 177 list($value, $unit, $multiplier) = $subq->ap->apply_units($response, ''); 178 $matchinganswer = $subq->get_matching_answer($value, 1); 179 } else { 180 $matchinganswer = $subq->get_matching_answer($response); 181 } 182 183 if (!$matchinganswer) { 184 if (is_null($response) || $response === '') { 185 $matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML); 186 } else { 187 $matchinganswer = new question_answer(0, '', 0.0, '', FORMAT_HTML); 188 } 189 } 190 191 // Work out a good input field size. 192 $size = max(1, core_text::strlen(trim($response)) + 1); 193 foreach ($subq->answers as $ans) { 194 $size = max($size, core_text::strlen(trim($ans->answer))); 195 } 196 $size = min(60, round($size + rand(0, $size*0.15))); 197 // The rand bit is to make guessing harder. 198 199 $inputattributes = array( 200 'type' => 'text', 201 'name' => $qa->get_qt_field_name($fieldname), 202 'value' => $response, 203 'id' => $qa->get_qt_field_name($fieldname), 204 'size' => $size, 205 ); 206 if ($options->readonly) { 207 $inputattributes['readonly'] = 'readonly'; 208 } 209 210 $feedbackimg = ''; 211 if ($options->correctness) { 212 $inputattributes['class'] = $this->feedback_class($matchinganswer->fraction); 213 $feedbackimg = $this->feedback_image($matchinganswer->fraction); 214 } 215 216 if ($subq->qtype->name() == 'shortanswer') { 217 $correctanswer = $subq->get_matching_answer($subq->get_correct_response()); 218 } else { 219 $correctanswer = $subq->get_correct_answer(); 220 } 221 222 $feedbackpopup = $this->feedback_popup($subq, $matchinganswer->fraction, 223 $subq->format_text($matchinganswer->feedback, $matchinganswer->feedbackformat, 224 $qa, 'question', 'answerfeedback', $matchinganswer->id), 225 s($correctanswer->answer), $options); 226 227 $output = html_writer::start_tag('span', array('class' => 'subquestion')); 228 $output .= html_writer::tag('label', get_string('answer'), 229 array('class' => 'subq accesshide', 'for' => $inputattributes['id'])); 230 $output .= html_writer::empty_tag('input', $inputattributes); 231 $output .= $feedbackimg; 232 $output .= $feedbackpopup; 233 $output .= html_writer::end_tag('span'); 234 235 return $output; 236 } 237 } 238 239 240 /** 241 * Render an embedded multiple-choice question that is displayed as a select menu. 242 * 243 * @copyright 2011 The Open University 244 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 245 */ 246 class qtype_multianswer_multichoice_inline_renderer 247 extends qtype_multianswer_subq_renderer_base { 248 249 public function subquestion(question_attempt $qa, question_display_options $options, 250 $index, question_graded_automatically $subq) { 251 252 $fieldprefix = 'sub' . $index . '_'; 253 $fieldname = $fieldprefix . 'answer'; 254 255 $response = $qa->get_last_qt_var($fieldname); 256 $choices = array(); 257 $matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML); 258 $rightanswer = null; 259 foreach ($subq->get_order($qa) as $value => $ansid) { 260 $ans = $subq->answers[$ansid]; 261 $choices[$value] = $subq->format_text($ans->answer, $ans->answerformat, 262 $qa, 'question', 'answer', $ansid); 263 if ($subq->is_choice_selected($response, $value)) { 264 $matchinganswer = $ans; 265 } 266 } 267 268 $inputattributes = array( 269 'id' => $qa->get_qt_field_name($fieldname), 270 ); 271 if ($options->readonly) { 272 $inputattributes['disabled'] = 'disabled'; 273 } 274 275 $feedbackimg = ''; 276 if ($options->correctness) { 277 $inputattributes['class'] = $this->feedback_class($matchinganswer->fraction); 278 $feedbackimg = $this->feedback_image($matchinganswer->fraction); 279 } 280 $select = html_writer::select($choices, $qa->get_qt_field_name($fieldname), 281 $response, array('' => ''), $inputattributes); 282 283 $order = $subq->get_order($qa); 284 $correctresponses = $subq->get_correct_response(); 285 $rightanswer = $subq->answers[$order[reset($correctresponses)]]; 286 if (!$matchinganswer) { 287 $matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML); 288 } 289 $feedbackpopup = $this->feedback_popup($subq, $matchinganswer->fraction, 290 $subq->format_text($matchinganswer->feedback, $matchinganswer->feedbackformat, 291 $qa, 'question', 'answerfeedback', $matchinganswer->id), 292 $subq->format_text($rightanswer->answer, $rightanswer->answerformat, 293 $qa, 'question', 'answer', $rightanswer->id), $options); 294 295 $output = html_writer::start_tag('span', array('class' => 'subquestion')); 296 $output .= html_writer::tag('label', get_string('answer'), 297 array('class' => 'subq accesshide', 'for' => $inputattributes['id'])); 298 $output .= $select; 299 $output .= $feedbackimg; 300 $output .= $feedbackpopup; 301 $output .= html_writer::end_tag('span'); 302 303 return $output; 304 } 305 } 306 307 308 /** 309 * Render an embedded multiple-choice question vertically, like for a normal 310 * multiple-choice question. 311 * 312 * @copyright 2010 Pierre Pichet 313 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 314 */ 315 class qtype_multianswer_multichoice_vertical_renderer extends qtype_multianswer_subq_renderer_base { 316 317 public function subquestion(question_attempt $qa, question_display_options $options, 318 $index, question_graded_automatically $subq) { 319 320 $fieldprefix = 'sub' . $index . '_'; 321 $fieldname = $fieldprefix . 'answer'; 322 $response = $qa->get_last_qt_var($fieldname); 323 324 $inputattributes = array( 325 'type' => 'radio', 326 'name' => $qa->get_qt_field_name($fieldname), 327 ); 328 if ($options->readonly) { 329 $inputattributes['disabled'] = 'disabled'; 330 } 331 332 $result = $this->all_choices_wrapper_start(); 333 $fraction = null; 334 foreach ($subq->get_order($qa) as $value => $ansid) { 335 $ans = $subq->answers[$ansid]; 336 337 $inputattributes['value'] = $value; 338 $inputattributes['id'] = $inputattributes['name'] . $value; 339 340 $isselected = $subq->is_choice_selected($response, $value); 341 if ($isselected) { 342 $inputattributes['checked'] = 'checked'; 343 $fraction = $ans->fraction; 344 } else { 345 unset($inputattributes['checked']); 346 } 347 348 $class = 'r' . ($value % 2); 349 if ($options->correctness && $isselected) { 350 $feedbackimg = $this->feedback_image($ans->fraction); 351 $class .= ' ' . $this->feedback_class($ans->fraction); 352 } else { 353 $feedbackimg = ''; 354 } 355 356 $result .= $this->choice_wrapper_start($class); 357 $result .= html_writer::empty_tag('input', $inputattributes); 358 $result .= html_writer::tag('label', $subq->format_text($ans->answer, 359 $ans->answerformat, $qa, 'question', 'answer', $ansid), 360 array('for' => $inputattributes['id'])); 361 $result .= $feedbackimg; 362 363 if ($options->feedback && $isselected && trim($ans->feedback)) { 364 $result .= html_writer::tag('div', 365 $subq->format_text($ans->feedback, $ans->feedbackformat, 366 $qa, 'question', 'answerfeedback', $ansid), 367 array('class' => 'specificfeedback')); 368 } 369 370 $result .= $this->choice_wrapper_end(); 371 } 372 373 $result .= $this->all_choices_wrapper_end(); 374 375 $feedback = array(); 376 if ($options->feedback && $options->marks >= question_display_options::MARK_AND_MAX && 377 $subq->maxmark > 0) { 378 $a = new stdClass(); 379 $a->mark = format_float($fraction * $subq->maxmark, $options->markdp); 380 $a->max = format_float($subq->maxmark, $options->markdp); 381 382 $feedback[] = html_writer::tag('div', get_string('markoutofmax', 'question', $a)); 383 } 384 385 if ($options->rightanswer) { 386 foreach ($subq->answers as $ans) { 387 if (question_state::graded_state_for_fraction($ans->fraction) == 388 question_state::$gradedright) { 389 $feedback[] = get_string('correctansweris', 'qtype_multichoice', 390 $subq->format_text($ans->answer, $ans->answerformat, 391 $qa, 'question', 'answer', $ansid)); 392 break; 393 } 394 } 395 } 396 397 $result .= html_writer::nonempty_tag('div', implode('<br />', $feedback), array('class' => 'outcome')); 398 399 return $result; 400 } 401 402 /** 403 * @param string $class class attribute value. 404 * @return string HTML to go before each choice. 405 */ 406 protected function choice_wrapper_start($class) { 407 return html_writer::start_tag('div', array('class' => $class)); 408 } 409 410 /** 411 * @return string HTML to go after each choice. 412 */ 413 protected function choice_wrapper_end() { 414 return html_writer::end_tag('div'); 415 } 416 417 /** 418 * @return string HTML to go before all the choices. 419 */ 420 protected function all_choices_wrapper_start() { 421 return html_writer::start_tag('div', array('class' => 'answer')); 422 } 423 424 /** 425 * @return string HTML to go after all the choices. 426 */ 427 protected function all_choices_wrapper_end() { 428 return html_writer::end_tag('div'); 429 } 430 } 431 432 433 /** 434 * Render an embedded multiple-choice question vertically, like for a normal 435 * multiple-choice question. 436 * 437 * @copyright 2010 Pierre Pichet 438 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 439 */ 440 class qtype_multianswer_multichoice_horizontal_renderer 441 extends qtype_multianswer_multichoice_vertical_renderer { 442 443 protected function choice_wrapper_start($class) { 444 return html_writer::start_tag('td', array('class' => $class)); 445 } 446 447 protected function choice_wrapper_end() { 448 return html_writer::end_tag('td'); 449 } 450 451 protected function all_choices_wrapper_start() { 452 return html_writer::start_tag('table', array('class' => 'answer')) . 453 html_writer::start_tag('tbody') . html_writer::start_tag('tr'); 454 } 455 456 protected function all_choices_wrapper_end() { 457 return html_writer::end_tag('tr') . html_writer::end_tag('tbody') . 458 html_writer::end_tag('table'); 459 } 460 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |