[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/question/type/multianswer/ -> renderer.php (source)

   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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1