[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/question/type/multichoice/ -> question.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   * Multiple choice question definition classes.
  19   *
  20   * @package    qtype
  21   * @subpackage multichoice
  22   * @copyright  2009 The Open University
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  require_once($CFG->dirroot . '/question/type/questionbase.php');
  30  
  31  /**
  32   * Base class for multiple choice questions. The parts that are common to
  33   * single select and multiple select.
  34   *
  35   * @copyright  2009 The Open University
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  abstract class qtype_multichoice_base extends question_graded_automatically {
  39      const LAYOUT_DROPDOWN = 0;
  40      const LAYOUT_VERTICAL = 1;
  41      const LAYOUT_HORIZONTAL = 2;
  42  
  43      public $answers;
  44  
  45      public $shuffleanswers;
  46      public $answernumbering;
  47      public $layout = self::LAYOUT_VERTICAL;
  48  
  49      public $correctfeedback;
  50      public $correctfeedbackformat;
  51      public $partiallycorrectfeedback;
  52      public $partiallycorrectfeedbackformat;
  53      public $incorrectfeedback;
  54      public $incorrectfeedbackformat;
  55  
  56      protected $order = null;
  57  
  58      public function start_attempt(question_attempt_step $step, $variant) {
  59          $this->order = array_keys($this->answers);
  60          if ($this->shuffleanswers) {
  61              shuffle($this->order);
  62          }
  63          $step->set_qt_var('_order', implode(',', $this->order));
  64      }
  65  
  66      public function apply_attempt_state(question_attempt_step $step) {
  67          $this->order = explode(',', $step->get_qt_var('_order'));
  68  
  69          // Add any missing answers. Sometimes people edit questions after they
  70          // have been attempted which breaks things.
  71          foreach ($this->order as $ansid) {
  72              if (isset($this->answers[$ansid])) {
  73                  continue;
  74              }
  75              $a = new stdClass();
  76              $a->id = 0;
  77              $a->answer = html_writer::span(get_string('deletedchoice', 'qtype_multichoice'),
  78                      'notifyproblem');
  79              $a->answerformat = FORMAT_HTML;
  80              $a->fraction = 0;
  81              $a->feedback = '';
  82              $a->feedbackformat = FORMAT_HTML;
  83              $this->answers[$ansid] = $this->qtype->make_answer($a);
  84              $this->answers[$ansid]->answerformat = FORMAT_HTML;
  85          }
  86      }
  87  
  88      public function get_question_summary() {
  89          $question = $this->html_to_text($this->questiontext, $this->questiontextformat);
  90          $choices = array();
  91          foreach ($this->order as $ansid) {
  92              $choices[] = $this->html_to_text($this->answers[$ansid]->answer,
  93                      $this->answers[$ansid]->answerformat);
  94          }
  95          return $question . ': ' . implode('; ', $choices);
  96      }
  97  
  98      public function get_order(question_attempt $qa) {
  99          $this->init_order($qa);
 100          return $this->order;
 101      }
 102  
 103      protected function init_order(question_attempt $qa) {
 104          if (is_null($this->order)) {
 105              $this->order = explode(',', $qa->get_step(0)->get_qt_var('_order'));
 106          }
 107      }
 108  
 109      public abstract function get_response(question_attempt $qa);
 110  
 111      public abstract function is_choice_selected($response, $value);
 112  
 113      public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) {
 114          if ($component == 'question' && in_array($filearea,
 115                  array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) {
 116              return $this->check_combined_feedback_file_access($qa, $options, $filearea);
 117  
 118          } else if ($component == 'question' && $filearea == 'answer') {
 119              $answerid = reset($args); // Itemid is answer id.
 120              return  in_array($answerid, $this->order);
 121  
 122          } else if ($component == 'question' && $filearea == 'answerfeedback') {
 123              $answerid = reset($args); // Itemid is answer id.
 124              $response = $this->get_response($qa);
 125              $isselected = false;
 126              foreach ($this->order as $value => $ansid) {
 127                  if ($ansid == $answerid) {
 128                      $isselected = $this->is_choice_selected($response, $value);
 129                      break;
 130                  }
 131              }
 132              // Param $options->suppresschoicefeedback is a hack specific to the
 133              // oumultiresponse question type. It would be good to refactor to
 134              // avoid refering to it here.
 135              return $options->feedback && empty($options->suppresschoicefeedback) &&
 136                      $isselected;
 137  
 138          } else if ($component == 'question' && $filearea == 'hint') {
 139              return $this->check_hint_file_access($qa, $options, $args);
 140  
 141          } else {
 142              return parent::check_file_access($qa, $options, $component, $filearea,
 143                      $args, $forcedownload);
 144          }
 145      }
 146  }
 147  
 148  
 149  /**
 150   * Represents a multiple choice question where only one choice should be selected.
 151   *
 152   * @copyright  2009 The Open University
 153   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 154   */
 155  class qtype_multichoice_single_question extends qtype_multichoice_base {
 156      public function get_renderer(moodle_page $page) {
 157          return $page->get_renderer('qtype_multichoice', 'single');
 158      }
 159  
 160      public function get_min_fraction() {
 161          $minfraction = 0;
 162          foreach ($this->answers as $ans) {
 163              $minfraction = min($minfraction, $ans->fraction);
 164          }
 165          return $minfraction;
 166      }
 167  
 168      /**
 169       * Return an array of the question type variables that could be submitted
 170       * as part of a question of this type, with their types, so they can be
 171       * properly cleaned.
 172       * @return array variable name => PARAM_... constant.
 173       */
 174      public function get_expected_data() {
 175          return array('answer' => PARAM_INT);
 176      }
 177  
 178      public function summarise_response(array $response) {
 179          if (!array_key_exists('answer', $response) ||
 180                  !array_key_exists($response['answer'], $this->order)) {
 181              return null;
 182          }
 183          $ansid = $this->order[$response['answer']];
 184          return $this->html_to_text($this->answers[$ansid]->answer,
 185                  $this->answers[$ansid]->answerformat);
 186      }
 187  
 188      public function classify_response(array $response) {
 189          if (!array_key_exists('answer', $response) ||
 190                  !array_key_exists($response['answer'], $this->order)) {
 191              return array($this->id => question_classified_response::no_response());
 192          }
 193          $choiceid = $this->order[$response['answer']];
 194          $ans = $this->answers[$choiceid];
 195          return array($this->id => new question_classified_response($choiceid,
 196                  $this->html_to_text($ans->answer, $ans->answerformat), $ans->fraction));
 197      }
 198  
 199      public function get_correct_response() {
 200          foreach ($this->order as $key => $answerid) {
 201              if (question_state::graded_state_for_fraction(
 202                      $this->answers[$answerid]->fraction)->is_correct()) {
 203                  return array('answer' => $key);
 204              }
 205          }
 206          return array();
 207      }
 208  
 209      public function prepare_simulated_post_data($simulatedresponse) {
 210          $ansid = 0;
 211          foreach ($this->answers as $answer) {
 212              if (clean_param($answer->answer, PARAM_NOTAGS) == $simulatedresponse['answer']) {
 213                  $ansid = $answer->id;
 214              }
 215          }
 216          if ($ansid) {
 217              return array('answer' => array_search($ansid, $this->order));
 218          } else {
 219              return array();
 220          }
 221      }
 222  
 223      public function get_student_response_values_for_simulation($postdata) {
 224          if (!isset($postdata['answer'])) {
 225              return array();
 226          } else {
 227              $answer = $this->answers[$this->order[$postdata['answer']]];
 228              return array('answer' => clean_param($answer->answer, PARAM_NOTAGS));
 229          }
 230      }
 231  
 232      public function is_same_response(array $prevresponse, array $newresponse) {
 233          return question_utils::arrays_same_at_key($prevresponse, $newresponse, 'answer');
 234      }
 235  
 236      public function is_complete_response(array $response) {
 237          return array_key_exists('answer', $response) && $response['answer'] !== '';
 238      }
 239  
 240      public function is_gradable_response(array $response) {
 241          return $this->is_complete_response($response);
 242      }
 243  
 244      public function grade_response(array $response) {
 245          if (array_key_exists('answer', $response) &&
 246                  array_key_exists($response['answer'], $this->order)) {
 247              $fraction = $this->answers[$this->order[$response['answer']]]->fraction;
 248          } else {
 249              $fraction = 0;
 250          }
 251          return array($fraction, question_state::graded_state_for_fraction($fraction));
 252      }
 253  
 254      public function get_validation_error(array $response) {
 255          if ($this->is_gradable_response($response)) {
 256              return '';
 257          }
 258          return get_string('pleaseselectananswer', 'qtype_multichoice');
 259      }
 260  
 261      public function get_response(question_attempt $qa) {
 262          return $qa->get_last_qt_var('answer', -1);
 263      }
 264  
 265      public function is_choice_selected($response, $value) {
 266          return (string) $response === (string) $value;
 267      }
 268  }
 269  
 270  
 271  /**
 272   * Represents a multiple choice question where multiple choices can be selected.
 273   *
 274   * @copyright  2009 The Open University
 275   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 276   */
 277  class qtype_multichoice_multi_question extends qtype_multichoice_base {
 278      public function get_renderer(moodle_page $page) {
 279          return $page->get_renderer('qtype_multichoice', 'multi');
 280      }
 281  
 282      public function get_min_fraction() {
 283          return 0;
 284      }
 285  
 286      public function clear_wrong_from_response(array $response) {
 287          foreach ($this->order as $key => $ans) {
 288              if (array_key_exists($this->field($key), $response) &&
 289                      question_state::graded_state_for_fraction(
 290                      $this->answers[$ans]->fraction)->is_incorrect()) {
 291                  $response[$this->field($key)] = 0;
 292              }
 293          }
 294          return $response;
 295      }
 296  
 297      public function get_num_parts_right(array $response) {
 298          $numright = 0;
 299          foreach ($this->order as $key => $ans) {
 300              $fieldname = $this->field($key);
 301              if (!array_key_exists($fieldname, $response) || !$response[$fieldname]) {
 302                  continue;
 303              }
 304  
 305              if (!question_state::graded_state_for_fraction(
 306                      $this->answers[$ans]->fraction)->is_incorrect()) {
 307                  $numright += 1;
 308              }
 309          }
 310          return array($numright, count($this->order));
 311      }
 312  
 313      /**
 314       * @param int $key choice number
 315       * @return string the question-type variable name.
 316       */
 317      protected function field($key) {
 318          return 'choice' . $key;
 319      }
 320  
 321      public function get_expected_data() {
 322          $expected = array();
 323          foreach ($this->order as $key => $notused) {
 324              $expected[$this->field($key)] = PARAM_BOOL;
 325          }
 326          return $expected;
 327      }
 328  
 329      public function summarise_response(array $response) {
 330          $selectedchoices = array();
 331          foreach ($this->order as $key => $ans) {
 332              $fieldname = $this->field($key);
 333              if (array_key_exists($fieldname, $response) && $response[$fieldname]) {
 334                  $selectedchoices[] = $this->html_to_text($this->answers[$ans]->answer,
 335                          $this->answers[$ans]->answerformat);
 336              }
 337          }
 338          if (empty($selectedchoices)) {
 339              return null;
 340          }
 341          return implode('; ', $selectedchoices);
 342      }
 343  
 344      public function classify_response(array $response) {
 345          $selectedchoices = array();
 346          foreach ($this->order as $key => $ansid) {
 347              $fieldname = $this->field($key);
 348              if (array_key_exists($fieldname, $response) && $response[$fieldname]) {
 349                  $selectedchoices[$ansid] = 1;
 350              }
 351          }
 352          $choices = array();
 353          foreach ($this->answers as $ansid => $ans) {
 354              if (isset($selectedchoices[$ansid])) {
 355                  $choices[$ansid] = new question_classified_response($ansid,
 356                          $this->html_to_text($ans->answer, $ans->answerformat), $ans->fraction);
 357              }
 358          }
 359          return $choices;
 360      }
 361  
 362      public function get_correct_response() {
 363          $response = array();
 364          foreach ($this->order as $key => $ans) {
 365              if (!question_state::graded_state_for_fraction(
 366                      $this->answers[$ans]->fraction)->is_incorrect()) {
 367                  $response[$this->field($key)] = 1;
 368              }
 369          }
 370          return $response;
 371      }
 372  
 373      public function prepare_simulated_post_data($simulatedresponse) {
 374          $postdata = array();
 375          foreach ($simulatedresponse as $ans => $checked) {
 376              foreach ($this->answers as $ansid => $answer) {
 377                  if (clean_param($answer->answer, PARAM_NOTAGS) == $ans) {
 378                      $fieldno = array_search($ansid, $this->order);
 379                      $postdata[$this->field($fieldno)] = $checked;
 380                      break;
 381                  }
 382              }
 383          }
 384          return $postdata;
 385      }
 386  
 387      public function get_student_response_values_for_simulation($postdata) {
 388          $simulatedresponse = array();
 389          foreach ($this->order as $fieldno => $ansid) {
 390              if (isset($postdata[$this->field($fieldno)])) {
 391                  $checked = $postdata[$this->field($fieldno)];
 392                  $simulatedresponse[clean_param($this->answers[$ansid]->answer, PARAM_NOTAGS)] = $checked;
 393              }
 394          }
 395          ksort($simulatedresponse);
 396          return $simulatedresponse;
 397      }
 398  
 399      public function is_same_response(array $prevresponse, array $newresponse) {
 400          foreach ($this->order as $key => $notused) {
 401              $fieldname = $this->field($key);
 402              if (!question_utils::arrays_same_at_key_integer($prevresponse, $newresponse, $fieldname)) {
 403                  return false;
 404              }
 405          }
 406          return true;
 407      }
 408  
 409      public function is_complete_response(array $response) {
 410          foreach ($this->order as $key => $notused) {
 411              if (!empty($response[$this->field($key)])) {
 412                  return true;
 413              }
 414          }
 415          return false;
 416      }
 417  
 418      public function is_gradable_response(array $response) {
 419          return $this->is_complete_response($response);
 420      }
 421  
 422      /**
 423       * @param array $response responses, as returned by
 424       *      {@link question_attempt_step::get_qt_data()}.
 425       * @return int the number of choices that were selected. in this response.
 426       */
 427      public function get_num_selected_choices(array $response) {
 428          $numselected = 0;
 429          foreach ($response as $key => $value) {
 430              // Response keys starting with _ are internal values like _order, so ignore them.
 431              if (!empty($value) && $key[0] != '_') {
 432                  $numselected += 1;
 433              }
 434          }
 435          return $numselected;
 436      }
 437  
 438      /**
 439       * @return int the number of choices that are correct.
 440       */
 441      public function get_num_correct_choices() {
 442          $numcorrect = 0;
 443          foreach ($this->answers as $ans) {
 444              if (!question_state::graded_state_for_fraction($ans->fraction)->is_incorrect()) {
 445                  $numcorrect += 1;
 446              }
 447          }
 448          return $numcorrect;
 449      }
 450  
 451      public function grade_response(array $response) {
 452          $fraction = 0;
 453          foreach ($this->order as $key => $ansid) {
 454              if (!empty($response[$this->field($key)])) {
 455                  $fraction += $this->answers[$ansid]->fraction;
 456              }
 457          }
 458          $fraction = min(max(0, $fraction), 1.0);
 459          return array($fraction, question_state::graded_state_for_fraction($fraction));
 460      }
 461  
 462      public function get_validation_error(array $response) {
 463          if ($this->is_gradable_response($response)) {
 464              return '';
 465          }
 466          return get_string('pleaseselectatleastoneanswer', 'qtype_multichoice');
 467      }
 468  
 469      /**
 470       * Disable those hint settings that we don't want when the student has selected
 471       * more choices than the number of right choices. This avoids giving the game away.
 472       * @param question_hint_with_parts $hint a hint.
 473       */
 474      protected function disable_hint_settings_when_too_many_selected(
 475              question_hint_with_parts $hint) {
 476          $hint->clearwrong = false;
 477      }
 478  
 479      public function get_hint($hintnumber, question_attempt $qa) {
 480          $hint = parent::get_hint($hintnumber, $qa);
 481          if (is_null($hint)) {
 482              return $hint;
 483          }
 484  
 485          if ($this->get_num_selected_choices($qa->get_last_qt_data()) >
 486                  $this->get_num_correct_choices()) {
 487              $hint = clone($hint);
 488              $this->disable_hint_settings_when_too_many_selected($hint);
 489          }
 490          return $hint;
 491      }
 492  
 493      public function get_response(question_attempt $qa) {
 494          return $qa->get_last_qt_data();
 495      }
 496  
 497      public function is_choice_selected($response, $value) {
 498          return !empty($response['choice' . $value]);
 499      }
 500  }


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