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