Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   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   * Test helpers for the multianswer question type.
  19   *
  20   * @package    qtype_multianswer
  21   * @copyright  2013 The Open University
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  require_once($CFG->dirroot . '/question/type/multianswer/question.php');
  30  
  31  
  32  /**
  33   * Test helper class for the multianswer question type.
  34   *
  35   * @copyright  2013 The Open University
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class qtype_multianswer_test_helper extends question_test_helper {
  39      public function get_test_questions() {
  40          return array('twosubq', 'fourmc', 'numericalzero', 'dollarsigns', 'multiple', 'zeroweight');
  41      }
  42  
  43      /**
  44       * Makes a multianswer question about completing two blanks in some text.
  45       * @return qtype_multianswer_question
  46       */
  47      public function make_multianswer_question_twosubq() {
  48          question_bank::load_question_definition_classes('multianswer');
  49          $q = new qtype_multianswer_question();
  50          test_question_maker::initialise_a_question($q);
  51          $q->name = 'Simple multianswer';
  52          $q->questiontext =
  53                  'Complete this opening line of verse: "The {#1} and the {#2} went to sea".';
  54          $q->generalfeedback = 'General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: ' .
  55                  '"The owl and the pussycat went to sea';
  56          $q->qtype = question_bank::get_qtype('multianswer');
  57  
  58          $q->textfragments = array(
  59              'Complete this opening line of verse: "The ',
  60              ' and the ',
  61              ' went to sea".',
  62          );
  63          $q->places = array('1' => '1', '2' => '2');
  64  
  65          // Shortanswer subquestion.
  66          question_bank::load_question_definition_classes('shortanswer');
  67          $sa = new qtype_shortanswer_question();
  68          test_question_maker::initialise_a_question($sa);
  69          $sa->name = 'Simple multianswer';
  70          $sa->questiontext = '{1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer}';
  71          $sa->questiontextformat = FORMAT_HTML;
  72          $sa->generalfeedback = '';
  73          $sa->generalfeedbackformat = FORMAT_HTML;
  74          $sa->usecase = true;
  75          $sa->answers = array(
  76              13 => new question_answer(13, 'Dog', 0.0, 'Wrong, silly!', FORMAT_HTML),
  77              14 => new question_answer(14, 'Owl', 1.0, 'Well done!', FORMAT_HTML),
  78              15 => new question_answer(15, '*', 0.0, 'Wrong answer', FORMAT_HTML),
  79          );
  80          $sa->qtype = question_bank::get_qtype('shortanswer');
  81          $sa->defaultmark = 1;
  82  
  83          // Multiple-choice subquestion.
  84          question_bank::load_question_definition_classes('multichoice');
  85          $mc = new qtype_multichoice_single_question();
  86          test_question_maker::initialise_a_question($mc);
  87          $mc->name = 'Simple multianswer';
  88          $mc->questiontext = '{1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!' .
  89                  '~Wiggly worm#Now you are just being rediculous!~=Pussy-cat#Well done!}';
  90          $mc->questiontextformat = FORMAT_HTML;
  91          $mc->generalfeedback = '';
  92          $mc->generalfeedbackformat = FORMAT_HTML;
  93  
  94          $mc->shuffleanswers = 0;
  95          $mc->answernumbering = 'none';
  96          $mc->layout = qtype_multichoice_base::LAYOUT_DROPDOWN;
  97  
  98          $mc->answers = array(
  99              13 => new question_answer(13, 'Bow-wow', 0,
 100                      'You seem to have a dog obsessions!', FORMAT_HTML),
 101              14 => new question_answer(14, 'Wiggly worm', 0,
 102                      'Now you are just being rediculous!', FORMAT_HTML),
 103              15 => new question_answer(15, 'Pussy-cat', 1,
 104                      'Well done!', FORMAT_HTML),
 105          );
 106          $mc->qtype = question_bank::get_qtype('multichoice');
 107          $mc->defaultmark = 1;
 108  
 109          $q->subquestions = array(
 110              1 => $sa,
 111              2 => $mc,
 112          );
 113  
 114          return $q;
 115      }
 116  
 117      /**
 118       * Makes a multianswer question about completing two blanks in some text.
 119       * @return object the question definition data, as it might be returned from
 120       * get_question_options.
 121       */
 122      public function get_multianswer_question_data_twosubq() {
 123          $qdata = new stdClass();
 124          test_question_maker::initialise_question_data($qdata);
 125  
 126          $qdata->name = 'Simple multianswer';
 127          $qdata->questiontext =
 128                          'Complete this opening line of verse: "The {#1} and the {#2} went to sea".';
 129          $qdata->generalfeedback = 'General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: ' .
 130                          '"The owl and the pussycat went to sea';
 131  
 132          $qdata->defaultmark = 2.0;
 133          $qdata->qtype = 'multianswer';
 134          $qdata->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
 135  
 136          $sa = new stdClass();
 137          test_question_maker::initialise_question_data($sa);
 138  
 139          $sa->name = 'Simple multianswer';
 140          $sa->questiontext = '{1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer}';
 141          $sa->generalfeedback = '';
 142          $sa->penalty = 0.0;
 143          $sa->qtype = 'shortanswer';
 144          $sa->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
 145  
 146          $sa->options = new stdClass();
 147          $sa->options->usecase = 0;
 148  
 149          $sa->options->answers = array(
 150              13 => new question_answer(13, 'Dog', 0, 'Wrong, silly!', FORMAT_HTML),
 151              14 => new question_answer(14, 'Owl', 1, 'Well done!',    FORMAT_HTML),
 152              15 => new question_answer(15, '*',   0, 'Wrong answer',  FORMAT_HTML),
 153          );
 154  
 155          $mc = new stdClass();
 156          test_question_maker::initialise_question_data($mc);
 157  
 158          $mc->name = 'Simple multianswer';
 159          $mc->questiontext = '{1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!~' .
 160                  'Wiggly worm#Now you are just being ridiculous!~=Pussy-cat#Well done!}';
 161          $mc->generalfeedback = '';
 162          $mc->penalty = 0.0;
 163          $mc->qtype = 'multichoice';
 164          $mc->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
 165  
 166          $mc->options = new stdClass();
 167          $mc->options->layout = 0;
 168          $mc->options->single = 1;
 169          $mc->options->shuffleanswers = 0;
 170          $mc->options->correctfeedback = '';
 171          $mc->options->correctfeedbackformat = 1;
 172          $mc->options->partiallycorrectfeedback = '';
 173          $mc->options->partiallycorrectfeedbackformat = 1;
 174          $mc->options->incorrectfeedback = '';
 175          $mc->options->incorrectfeedbackformat = 1;
 176          $mc->options->answernumbering = 0;
 177          $mc->options->shownumcorrect = 0;
 178  
 179          $mc->options->answers = array(
 180              23 => new question_answer(23, 'Bow-wow',     0,
 181                      'You seem to have a dog obsessions!', FORMAT_HTML),
 182              24 => new question_answer(24, 'Wiggly worm', 0,
 183                      'Now you are just being ridiculous!', FORMAT_HTML),
 184              25 => new question_answer(25, 'Pussy-cat',   1,
 185                      'Well done!',                         FORMAT_HTML),
 186          );
 187  
 188          $qdata->options = new stdClass();
 189          $qdata->options->questions = array(
 190              1 => $sa,
 191              2 => $mc,
 192          );
 193  
 194          $qdata->hints = array(
 195              new question_hint_with_parts(0, 'Hint 1', FORMAT_HTML, 0, 0),
 196              new question_hint_with_parts(0, 'Hint 2', FORMAT_HTML, 0, 0),
 197          );
 198  
 199          return $qdata;
 200      }
 201  
 202      /**
 203       * Makes a multianswer question onetaining one blank in some text.
 204       * This question has no hints.
 205       *
 206       * @return object the question definition data, as it might be returned from
 207       * get_question_options.
 208       */
 209      public function get_multianswer_question_data_dollarsigns() {
 210          $qdata = new stdClass();
 211          test_question_maker::initialise_question_data($qdata);
 212  
 213          $qdata->name = 'Multianswer with $s';
 214          $qdata->questiontext =
 215                          'Which is the right order? {#1}';
 216          $qdata->generalfeedback = '';
 217  
 218          $qdata->defaultmark = 1.0;
 219          $qdata->qtype = 'multianswer';
 220          $qdata->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
 221  
 222          $mc = new stdClass();
 223          test_question_maker::initialise_question_data($mc);
 224  
 225          $mc->name = 'Multianswer with $s';
 226          $mc->questiontext = '{1:MULTICHOICE:=y,y,$3~$3,y,y}';
 227          $mc->generalfeedback = '';
 228          $mc->penalty = 0.0;
 229          $mc->qtype = 'multichoice';
 230          $mc->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
 231  
 232          $mc->options = new stdClass();
 233          $mc->options->layout = 0;
 234          $mc->options->single = 1;
 235          $mc->options->shuffleanswers = 0;
 236          $mc->options->correctfeedback = '';
 237          $mc->options->correctfeedbackformat = 1;
 238          $mc->options->partiallycorrectfeedback = '';
 239          $mc->options->partiallycorrectfeedbackformat = 1;
 240          $mc->options->incorrectfeedback = '';
 241          $mc->options->incorrectfeedbackformat = 1;
 242          $mc->options->answernumbering = 0;
 243          $mc->options->shownumcorrect = 0;
 244  
 245          $mc->options->answers = array(
 246              23 => new question_answer(23, 'y,y,$3', 0, '', FORMAT_HTML),
 247              24 => new question_answer(24, '$3,y,y', 0, '', FORMAT_HTML),
 248          );
 249  
 250          $qdata->options = new stdClass();
 251          $qdata->options->questions = array(
 252              1 => $mc,
 253          );
 254  
 255          $qdata->hints = array(
 256          );
 257  
 258          return $qdata;
 259      }
 260  
 261      /**
 262       * Makes a multianswer question about completing two blanks in some text.
 263       * @return object the question definition data, as it might be returned from
 264       *      the question editing form.
 265       */
 266      public function get_multianswer_question_form_data_twosubq() {
 267          $formdata = new stdClass();
 268          $formdata->name = 'Simple multianswer';
 269          $formdata->questiontext = array('text' => 'Complete this opening line of verse: "The ' .
 270                  '{1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer} ' .
 271                  'and the {1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!' .
 272                  '~Wiggly worm#Now you are just being ridiculous!~=Pussy-cat#Well done!}' .
 273                  ' went to sea".', 'format' => FORMAT_HTML);
 274          $formdata->generalfeedback = array('text' => 'General feedback: It\'s from "The Owl and the Pussy-cat" ' .
 275                  'by Lear: "The owl and the pussycat went to sea', 'format' => FORMAT_HTML);
 276  
 277          $formdata->hint = array(
 278              0 => array('text' => 'Hint 1', 'format' => FORMAT_HTML, 'itemid' => 0),
 279              1 => array('text' => 'Hint 2', 'format' => FORMAT_HTML, 'itemid' => 0),
 280          );
 281  
 282          $formdata->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
 283  
 284          return $formdata;
 285      }
 286  
 287      /**
 288       * Makes a multianswer question about completing two blanks in some text.
 289       * @return qtype_multianswer_question
 290       */
 291      public function make_multianswer_question_fourmc() {
 292          question_bank::load_question_definition_classes('multianswer');
 293          $q = new qtype_multianswer_question();
 294          test_question_maker::initialise_a_question($q);
 295          $q->name = 'Multianswer four multi-choice';
 296          $q->questiontext = '<p>Match the following cities with the correct state:</p>
 297                  <ul>
 298                  <li>San Francisco: {#1}</li>
 299                  <li>Tucson: {#2}</li>
 300                  <li>Los Angeles: {#3}</li>
 301                  <li>Phoenix: {#4}</li>
 302                  </ul>';
 303          $q->questiontextformat = FORMAT_HTML;
 304          $q->generalfeedback = '';
 305          $q->qtype = question_bank::get_qtype('multianswer');
 306  
 307          $q->textfragments = array('<p>Match the following cities with the correct state:</p>
 308                  <ul>
 309                  <li>San Francisco: ', '</li>
 310                  <li>Tucson: ', '</li>
 311                  <li>Los Angeles: ', '</li>
 312                  <li>Phoenix: ', '</li>
 313                  </ul>');
 314          $q->places = array('1' => '1', '2' => '2', '3' => '3', '4' => '4');
 315  
 316          $subqdata = array(
 317              1 => array('qt' => '{1:MULTICHOICE:=California#OK~Arizona#Wrong}', 'California' => 'OK', 'Arizona' => 'Wrong'),
 318              2 => array('qt' => '{1:MULTICHOICE:%0%California#Wrong~=Arizona#OK}', 'California' => 'Wrong', 'Arizona' => 'OK'),
 319              3 => array('qt' => '{1:MULTICHOICE:=California#OK~Arizona#Wrong}', 'California' => 'OK', 'Arizona' => 'Wrong'),
 320              4 => array('qt' => '{1:MULTICHOICE:%0%California#Wrong~=Arizona#OK}', 'California' => 'Wrong', 'Arizona' => 'OK'),
 321          );
 322          foreach ($subqdata as $i => $data) {
 323                  // Multiple-choice subquestion.
 324              question_bank::load_question_definition_classes('multichoice');
 325              $mc = new qtype_multichoice_single_question();
 326              test_question_maker::initialise_a_question($mc);
 327              $mc->name = 'Multianswer four multi-choice';
 328              $mc->questiontext = $data['qt'];
 329              $mc->questiontextformat = FORMAT_HTML;
 330              $mc->generalfeedback = '';
 331              $mc->generalfeedbackformat = FORMAT_HTML;
 332  
 333              $mc->shuffleanswers = 0; // TODO this is a cheat to make the unit tests easier to write.
 334              // In reality, multianswer questions always shuffle.
 335              $mc->answernumbering = 'none';
 336              $mc->layout = qtype_multichoice_base::LAYOUT_DROPDOWN;
 337  
 338              $mc->answers = array(
 339                  10 * $i     => new question_answer(13, 'California', (float) ($data['California'] == 'OK'),
 340                          $data['California'], FORMAT_HTML),
 341                  10 * $i + 1 => new question_answer(14, 'Arizona', (float) ($data['Arizona'] == 'OK'),
 342                           $data['Arizona'], FORMAT_HTML),
 343              );
 344              $mc->qtype = question_bank::get_qtype('multichoice');
 345              $mc->defaultmark = 1;
 346  
 347              $q->subquestions[$i] = $mc;
 348          }
 349  
 350          return $q;
 351      }
 352  
 353      /**
 354       * Makes a multianswer question with one numerical subquestion, right answer 0.
 355       * This is used for testing the MDL-35370 bug.
 356       * @return qtype_multianswer_question
 357       */
 358      public function make_multianswer_question_numericalzero() {
 359          question_bank::load_question_definition_classes('multianswer');
 360          $q = new qtype_multianswer_question();
 361          test_question_maker::initialise_a_question($q);
 362          $q->name = 'Numerical zero';
 363          $q->questiontext =
 364                  'Enter zero: {#1}.';
 365          $q->generalfeedback = '';
 366          $q->qtype = question_bank::get_qtype('multianswer');
 367  
 368          $q->textfragments = array(
 369              'Enter zero: ',
 370              '.',
 371          );
 372          $q->places = array('1' => '1');
 373  
 374          // Numerical subquestion.
 375          question_bank::load_question_definition_classes('numerical');
 376          $sub = new qtype_numerical_question();
 377          test_question_maker::initialise_a_question($sub);
 378          $sub->name = 'Numerical zero';
 379          $sub->questiontext = '{1:NUMERICAL:=0:0}';
 380          $sub->questiontextformat = FORMAT_HTML;
 381          $sub->generalfeedback = '';
 382          $sub->generalfeedbackformat = FORMAT_HTML;
 383          $sub->answers = array(
 384              13 => new qtype_numerical_answer(13, '0', 1.0, '', FORMAT_HTML, 0),
 385          );
 386          $sub->qtype = question_bank::get_qtype('numerical');
 387          $sub->ap = new qtype_numerical_answer_processor(array());
 388          $sub->defaultmark = 1;
 389  
 390          $q->subquestions = array(
 391              1 => $sub,
 392          );
 393  
 394          return $q;
 395      }
 396  
 397      /**
 398       * Makes a multianswer question with multichoice_multiple questions in it.
 399       * @return qtype_multianswer_question
 400       */
 401      public function make_multianswer_question_multiple() {
 402          question_bank::load_question_definition_classes('multianswer');
 403          $q = new qtype_multianswer_question();
 404          test_question_maker::initialise_a_question($q);
 405          $q->name = 'Multichoice multiple';
 406          $q->questiontext = 'Please select the fruits {#1} and vegetables {#2}';
 407          $q->generalfeedback = 'You should know which foods are fruits or vegetables.';
 408          $q->qtype = question_bank::get_qtype('multianswer');
 409  
 410          $q->textfragments = array(
 411              'Please select the fruits ',
 412              ' and vegetables ',
 413              ''
 414          );
 415          $q->places = array('1' => '1', '2' => '2');
 416  
 417          // Multiple-choice subquestion.
 418          question_bank::load_question_definition_classes('multichoice');
 419          $mc = new qtype_multichoice_multi_question();
 420          test_question_maker::initialise_a_question($mc);
 421          $mc->name = 'Multianswer 1';
 422          $mc->questiontext = '{1:MULTIRESPONSE:=Apple#Good~%-50%Burger~%-50%Hot dog#Not a fruit~%-50%Pizza' .
 423              '~=Orange#Correct~=Banana}';
 424          $mc->questiontextformat = FORMAT_HTML;
 425          $mc->generalfeedback = '';
 426          $mc->generalfeedbackformat = FORMAT_HTML;
 427  
 428          $mc->shuffleanswers = 0;
 429          $mc->answernumbering = 'none';
 430          $mc->layout = qtype_multichoice_base::LAYOUT_VERTICAL;
 431  
 432          $mc->answers = array(
 433              16 => new question_answer(16, 'Apple', 0.3333333,
 434                                        'Good', FORMAT_HTML),
 435              17 => new question_answer(17, 'Burger', -0.5,
 436                                        '', FORMAT_HTML),
 437              18 => new question_answer(18, 'Hot dog', -0.5,
 438                                        'Not a fruit', FORMAT_HTML),
 439              19 => new question_answer(19, 'Pizza', -0.5,
 440                                        '', FORMAT_HTML),
 441              20 => new question_answer(20, 'Orange', 0.3333333,
 442                                        'Correct', FORMAT_HTML),
 443              21 => new question_answer(21, 'Banana', 0.3333333,
 444                                        '', FORMAT_HTML),
 445          );
 446          $mc->qtype = question_bank::get_qtype('multichoice');
 447          $mc->defaultmark = 1;
 448  
 449          // Multiple-choice subquestion.
 450          question_bank::load_question_definition_classes('multichoice');
 451          $mc2 = new qtype_multichoice_multi_question();
 452          test_question_maker::initialise_a_question($mc2);
 453          $mc2->name = 'Multichoice 2';
 454          $mc2->questiontext = '{1:MULTIRESPONSE:=Raddish#Good~%-50%Chocolate~%-50%Biscuit#Not a vegetable~%-50%Cheese' .
 455              '~=Carrot#Correct}';
 456          $mc2->questiontextformat = FORMAT_HTML;
 457          $mc2->generalfeedback = '';
 458          $mc2->generalfeedbackformat = FORMAT_HTML;
 459  
 460          $mc2->shuffleanswers = 0;
 461          $mc2->answernumbering = 'none';
 462          $mc2->layout = qtype_multichoice_base::LAYOUT_VERTICAL;
 463  
 464          $mc2->answers = array(
 465              22 => new question_answer(22, 'Raddish', 0.5,
 466                                        'Good', FORMAT_HTML),
 467              23 => new question_answer(23, 'Chocolate', -0.5,
 468                                        '', FORMAT_HTML),
 469              24 => new question_answer(24, 'Biscuit', -0.5,
 470                                        'Not a vegetable', FORMAT_HTML),
 471              25 => new question_answer(25, 'Cheese', -0.5,
 472                                        '', FORMAT_HTML),
 473              26 => new question_answer(26, 'Carrot', 0.5,
 474                                        'Correct', FORMAT_HTML),
 475          );
 476          $mc2->qtype = question_bank::get_qtype('multichoice');
 477          $mc2->defaultmark = 1;
 478  
 479          $q->subquestions = array(
 480              1 => $mc,
 481              2 => $mc2,
 482          );
 483  
 484          return $q;
 485      }
 486  
 487      /**
 488       * Makes a multianswer question with zero weight.
 489       * This is used for testing the MDL-77378 bug.
 490       * @return qtype_multianswer_question
 491       */
 492      public function make_multianswer_question_zeroweight() {
 493          question_bank::load_question_definition_classes('multianswer');
 494          $q = new qtype_multianswer_question();
 495          test_question_maker::initialise_a_question($q);
 496          $q->name = 'Zero weight';
 497          $q->questiontext =
 498              'Optional question: {#1}.';
 499          $q->generalfeedback = '';
 500          $q->qtype = question_bank::get_qtype('multianswer');
 501          $q->textfragments = array(
 502              'Optional question: ',
 503              '.',
 504          );
 505          $q->places = array('1' => '1');
 506  
 507          // Shortanswer subquestion.
 508          question_bank::load_question_definition_classes('shortanswer');
 509          $sa = new qtype_shortanswer_question();
 510          test_question_maker::initialise_a_question($sa);
 511          $sa->name = 'Zero weight';
 512          $sa->questiontext = '{0:SHORTANSWER:~%0%Input box~%100%*}';
 513          $sa->questiontextformat = FORMAT_HTML;
 514          $sa->generalfeedback = '';
 515          $sa->generalfeedbackformat = FORMAT_HTML;
 516          $sa->usecase = true;
 517          $sa->answers = array(
 518              13 => new question_answer(13, 'Input box', 0.0, '', FORMAT_HTML),
 519              14 => new question_answer(14, '*', 1.0, '', FORMAT_HTML),
 520          );
 521          $sa->qtype = question_bank::get_qtype('shortanswer');
 522          $sa->defaultmark = 0;
 523  
 524          $q->subquestions = array(
 525              1 => $sa,
 526          );
 527  
 528          return $q;
 529      }
 530  
 531  }