[ Index ]

PHP Cross Reference of moodle-2.8

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


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1