Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   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   * Choice module library functions tests
  19   *
  20   * @package    mod_choice
  21   * @category   test
  22   * @copyright  2015 Juan Leyva <juan@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   * @since      Moodle 3.0
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  global $CFG;
  30  
  31  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  32  require_once($CFG->dirroot . '/mod/choice/lib.php');
  33  
  34  /**
  35   * Choice module library functions tests
  36   *
  37   * @package    mod_choice
  38   * @category   test
  39   * @copyright  2015 Juan Leyva <juan@moodle.com>
  40   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   * @since      Moodle 3.0
  42   */
  43  class mod_choice_lib_testcase extends externallib_advanced_testcase {
  44  
  45      /**
  46       * Test choice_view
  47       * @return void
  48       */
  49      public function test_choice_view() {
  50          global $CFG;
  51  
  52          $this->resetAfterTest();
  53  
  54          $this->setAdminUser();
  55          // Setup test data.
  56          $course = $this->getDataGenerator()->create_course();
  57          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
  58          $context = context_module::instance($choice->cmid);
  59          $cm = get_coursemodule_from_instance('choice', $choice->id);
  60  
  61          // Trigger and capture the event.
  62          $sink = $this->redirectEvents();
  63  
  64          choice_view($choice, $course, $cm, $context);
  65  
  66          $events = $sink->get_events();
  67          $this->assertCount(1, $events);
  68          $event = array_shift($events);
  69  
  70          // Checking that the event contains the expected values.
  71          $this->assertInstanceOf('\mod_choice\event\course_module_viewed', $event);
  72          $this->assertEquals($context, $event->get_context());
  73          $url = new \moodle_url('/mod/choice/view.php', array('id' => $cm->id));
  74          $this->assertEquals($url, $event->get_url());
  75          $this->assertEventContextNotUsed($event);
  76          $this->assertNotEmpty($event->get_name());
  77      }
  78  
  79      /**
  80       * Test choice_can_view_results
  81       * @return void
  82       */
  83      public function test_choice_can_view_results() {
  84          global $DB, $USER;
  85  
  86          $this->resetAfterTest();
  87  
  88          $this->setAdminUser();
  89          // Setup test data.
  90          $course = $this->getDataGenerator()->create_course();
  91          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
  92          $context = context_module::instance($choice->cmid);
  93          $cm = get_coursemodule_from_instance('choice', $choice->id);
  94  
  95          // Default values are false, user cannot view results.
  96          $canview = choice_can_view_results($choice);
  97          $this->assertFalse($canview);
  98  
  99          // Show results forced.
 100          $choice->showresults = CHOICE_SHOWRESULTS_ALWAYS;
 101          $DB->update_record('choice', $choice);
 102          $canview = choice_can_view_results($choice);
 103          $this->assertTrue($canview);
 104  
 105          // Add a time restriction (choice not open yet).
 106          $choice->timeopen = time() + YEARSECS;
 107          $DB->update_record('choice', $choice);
 108          $canview = choice_can_view_results($choice);
 109          $this->assertFalse($canview);
 110  
 111          // Show results after closing.
 112          $choice->timeopen = 0;
 113          $choice->showresults = CHOICE_SHOWRESULTS_AFTER_CLOSE;
 114          $DB->update_record('choice', $choice);
 115          $canview = choice_can_view_results($choice);
 116          $this->assertFalse($canview);
 117  
 118          $choice->timeclose = time() - HOURSECS;
 119          $DB->update_record('choice', $choice);
 120          $canview = choice_can_view_results($choice);
 121          $this->assertTrue($canview);
 122  
 123          // Show results after answering.
 124          $choice->timeclose = 0;
 125          $choice->showresults = CHOICE_SHOWRESULTS_AFTER_ANSWER;
 126          $DB->update_record('choice', $choice);
 127          $canview = choice_can_view_results($choice);
 128          $this->assertFalse($canview);
 129  
 130          // Get the first option.
 131          $choicewithoptions = choice_get_choice($choice->id);
 132          $optionids = array_keys($choicewithoptions->option);
 133  
 134          choice_user_submit_response($optionids[0], $choice, $USER->id, $course, $cm);
 135  
 136          $canview = choice_can_view_results($choice);
 137          $this->assertTrue($canview);
 138  
 139      }
 140  
 141      public function test_choice_user_submit_response_validation() {
 142          global $USER;
 143  
 144          $this->resetAfterTest();
 145  
 146          $this->setAdminUser();
 147          // Setup test data.
 148          $course = $this->getDataGenerator()->create_course();
 149          $choice1 = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
 150          $choice2 = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
 151          $cm = get_coursemodule_from_instance('choice', $choice1->id);
 152  
 153          $choicewithoptions1 = choice_get_choice($choice1->id);
 154          $choicewithoptions2 = choice_get_choice($choice2->id);
 155          $optionids1 = array_keys($choicewithoptions1->option);
 156          $optionids2 = array_keys($choicewithoptions2->option);
 157  
 158          // Make sure we cannot submit options from a different choice instance.
 159          $this->expectException(moodle_exception::class);
 160          choice_user_submit_response($optionids2[0], $choice1, $USER->id, $course, $cm);
 161      }
 162  
 163      /**
 164       * Test choice_get_user_response
 165       * @return void
 166       */
 167      public function test_choice_get_user_response() {
 168          $this->resetAfterTest();
 169  
 170          $this->setAdminUser();
 171          // Setup test data.
 172          $course = $this->getDataGenerator()->create_course();
 173          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 174          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
 175          $cm = get_coursemodule_from_instance('choice', $choice->id);
 176  
 177          $choicewithoptions = choice_get_choice($choice->id);
 178          $optionids = array_keys($choicewithoptions->option);
 179  
 180          choice_user_submit_response($optionids[0], $choice, $student->id, $course, $cm);
 181          $responses = choice_get_user_response($choice, $student->id);
 182          $this->assertCount(1, $responses);
 183          $response = array_shift($responses);
 184          $this->assertEquals($optionids[0], $response->optionid);
 185  
 186          // Multiple responses.
 187          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id, 'allowmultiple' => 1));
 188          $cm = get_coursemodule_from_instance('choice', $choice->id);
 189  
 190          $choicewithoptions = choice_get_choice($choice->id);
 191          $optionids = array_keys($choicewithoptions->option);
 192  
 193          // Submit a response with the options reversed.
 194          $selections = $optionids;
 195          rsort($selections);
 196          choice_user_submit_response($selections, $choice, $student->id, $course, $cm);
 197          $responses = choice_get_user_response($choice, $student->id);
 198          $this->assertCount(count($optionids), $responses);
 199          foreach ($responses as $resp) {
 200              $this->assertEquals(array_shift($optionids), $resp->optionid);
 201          }
 202      }
 203  
 204      /**
 205       * Test choice_get_my_response
 206       * @return void
 207       */
 208      public function test_choice_get_my_response() {
 209          global $USER;
 210  
 211          $this->resetAfterTest();
 212  
 213          $this->setAdminUser();
 214          // Setup test data.
 215          $course = $this->getDataGenerator()->create_course();
 216          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
 217          $cm = get_coursemodule_from_instance('choice', $choice->id);
 218  
 219          $choicewithoptions = choice_get_choice($choice->id);
 220          $optionids = array_keys($choicewithoptions->option);
 221  
 222          choice_user_submit_response($optionids[0], $choice, $USER->id, $course, $cm);
 223          $responses = choice_get_my_response($choice);
 224          $this->assertCount(1, $responses);
 225          $response = array_shift($responses);
 226          $this->assertEquals($optionids[0], $response->optionid);
 227  
 228          // Multiple responses.
 229          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id, 'allowmultiple' => 1));
 230          $cm = get_coursemodule_from_instance('choice', $choice->id);
 231  
 232          $choicewithoptions = choice_get_choice($choice->id);
 233          $optionids = array_keys($choicewithoptions->option);
 234  
 235          // Submit a response with the options reversed.
 236          $selections = $optionids;
 237          rsort($selections);
 238          choice_user_submit_response($selections, $choice, $USER->id, $course, $cm);
 239          $responses = choice_get_my_response($choice);
 240          $this->assertCount(count($optionids), $responses);
 241          foreach ($responses as $resp) {
 242              $this->assertEquals(array_shift($optionids), $resp->optionid);
 243          }
 244      }
 245  
 246      /**
 247       * Test choice_get_availability_status
 248       * @return void
 249       */
 250      public function test_choice_get_availability_status() {
 251          global $USER;
 252  
 253          $this->resetAfterTest();
 254  
 255          $this->setAdminUser();
 256          // Setup test data.
 257          $course = $this->getDataGenerator()->create_course();
 258          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
 259  
 260          // No time restrictions and updates allowed.
 261          list($status, $warnings) = choice_get_availability_status($choice, false);
 262          $this->assertEquals(true, $status);
 263          $this->assertCount(0, $warnings);
 264  
 265          // No updates allowed, but haven't answered yet.
 266          $choice->allowupdate = false;
 267          list($status, $warnings) = choice_get_availability_status($choice, false);
 268          $this->assertEquals(true, $status);
 269          $this->assertCount(0, $warnings);
 270  
 271          // No updates allowed and have answered.
 272          $cm = get_coursemodule_from_instance('choice', $choice->id);
 273          $choicewithoptions = choice_get_choice($choice->id);
 274          $optionids = array_keys($choicewithoptions->option);
 275          choice_user_submit_response($optionids[0], $choice, $USER->id, $course, $cm);
 276          list($status, $warnings) = choice_get_availability_status($choice, false);
 277          $this->assertEquals(false, $status);
 278          $this->assertCount(1, $warnings);
 279          $this->assertEquals('choicesaved', array_keys($warnings)[0]);
 280  
 281          $choice->allowupdate = true;
 282  
 283          // With time restrictions, still open.
 284          $choice->timeopen = time() - DAYSECS;
 285          $choice->timeclose = time() + DAYSECS;
 286          list($status, $warnings) = choice_get_availability_status($choice, false);
 287          $this->assertEquals(true, $status);
 288          $this->assertCount(0, $warnings);
 289  
 290          // Choice not open yet.
 291          $choice->timeopen = time() + DAYSECS;
 292          $choice->timeclose = $choice->timeopen + DAYSECS;
 293          list($status, $warnings) = choice_get_availability_status($choice, false);
 294          $this->assertEquals(false, $status);
 295          $this->assertCount(1, $warnings);
 296          $this->assertEquals('notopenyet', array_keys($warnings)[0]);
 297  
 298          // Choice closed.
 299          $choice->timeopen = time() - DAYSECS;
 300          $choice->timeclose = time() - 1;
 301          list($status, $warnings) = choice_get_availability_status($choice, false);
 302          $this->assertEquals(false, $status);
 303          $this->assertCount(1, $warnings);
 304          $this->assertEquals('expired', array_keys($warnings)[0]);
 305      }
 306  
 307      /*
 308       * The choice's event should not be shown to a user when the user cannot view the choice activity at all.
 309       */
 310      public function test_choice_core_calendar_provide_event_action_in_hidden_section() {
 311          global $CFG;
 312  
 313          $this->resetAfterTest();
 314  
 315          $this->setAdminUser();
 316  
 317          // Create a course.
 318          $course = $this->getDataGenerator()->create_course();
 319  
 320          // Create a student.
 321          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 322  
 323          // Create a choice.
 324          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 325                  'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
 326  
 327          // Create a calendar event.
 328          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
 329  
 330          // Set sections 0 as hidden.
 331          set_section_visible($course->id, 0, 0);
 332  
 333          // Now, log out.
 334          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 335          $this->setUser();
 336  
 337          // Create an action factory.
 338          $factory = new \core_calendar\action_factory();
 339  
 340          // Decorate action event for the student.
 341          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory, $student->id);
 342  
 343          // Confirm the event is not shown at all.
 344          $this->assertNull($actionevent);
 345      }
 346  
 347      /*
 348       * The choice's event should not be shown to a user who does not have permission to view the choice.
 349       */
 350      public function test_choice_core_calendar_provide_event_action_for_non_user() {
 351          global $CFG;
 352  
 353          $this->resetAfterTest();
 354  
 355          $this->setAdminUser();
 356  
 357          // Create a course.
 358          $course = $this->getDataGenerator()->create_course();
 359  
 360          // Create a choice.
 361          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 362                  'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
 363  
 364          // Create a calendar event.
 365          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
 366  
 367          // Now, log out.
 368          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 369          $this->setUser();
 370  
 371          // Create an action factory.
 372          $factory = new \core_calendar\action_factory();
 373  
 374          // Decorate action event.
 375          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory);
 376  
 377          // Confirm the event is not shown at all.
 378          $this->assertNull($actionevent);
 379      }
 380  
 381      public function test_choice_core_calendar_provide_event_action_open() {
 382          $this->resetAfterTest();
 383  
 384          $this->setAdminUser();
 385  
 386          // Create a course.
 387          $course = $this->getDataGenerator()->create_course();
 388  
 389          // Create a choice.
 390          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 391              'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
 392  
 393          // Create a calendar event.
 394          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
 395  
 396          // Create an action factory.
 397          $factory = new \core_calendar\action_factory();
 398  
 399          // Decorate action event.
 400          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory);
 401  
 402          // Confirm the event was decorated.
 403          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 404          $this->assertEquals(get_string('viewchoices', 'choice'), $actionevent->get_name());
 405          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 406          $this->assertEquals(1, $actionevent->get_item_count());
 407          $this->assertTrue($actionevent->is_actionable());
 408      }
 409  
 410      public function test_choice_core_calendar_provide_event_action_open_for_user() {
 411          $this->resetAfterTest();
 412  
 413          $this->setAdminUser();
 414  
 415          // Create a course.
 416          $course = $this->getDataGenerator()->create_course();
 417  
 418          // Create a student.
 419          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 420  
 421          // Create a choice.
 422          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 423              'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
 424  
 425          // Create a calendar event.
 426          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
 427  
 428          // Create an action factory.
 429          $factory = new \core_calendar\action_factory();
 430  
 431          // Decorate action event for the student.
 432          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory, $student->id);
 433  
 434          // Confirm the event was decorated.
 435          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 436          $this->assertEquals(get_string('viewchoices', 'choice'), $actionevent->get_name());
 437          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 438          $this->assertEquals(1, $actionevent->get_item_count());
 439          $this->assertTrue($actionevent->is_actionable());
 440      }
 441  
 442      /**
 443       * An event should not have an action if the user has already submitted a response
 444       * to the choice activity.
 445       */
 446      public function test_choice_core_calendar_provide_event_action_already_submitted() {
 447          $this->resetAfterTest();
 448  
 449          $this->setAdminUser();
 450  
 451          // Create a course.
 452          $course = $this->getDataGenerator()->create_course();
 453  
 454          // Create a student.
 455          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 456  
 457          // Create a choice.
 458          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 459              'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
 460          $cm = get_coursemodule_from_instance('choice', $choice->id);
 461  
 462          $choicewithoptions = choice_get_choice($choice->id);
 463          $optionids = array_keys($choicewithoptions->option);
 464  
 465          choice_user_submit_response($optionids[0], $choice, $student->id, $course, $cm);
 466  
 467          // Create a calendar event.
 468          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
 469  
 470          // Create an action factory.
 471          $factory = new \core_calendar\action_factory();
 472  
 473          $this->setUser($student);
 474  
 475          // Decorate action event.
 476          $action = mod_choice_core_calendar_provide_event_action($event, $factory);
 477  
 478          // Confirm no action was returned if the user has already submitted the
 479          // choice activity.
 480          $this->assertNull($action);
 481      }
 482  
 483      /**
 484       * An event should not have an action if the user has already submitted a response
 485       * to the choice activity.
 486       */
 487      public function test_choice_core_calendar_provide_event_action_already_submitted_for_user() {
 488          $this->resetAfterTest();
 489  
 490          $this->setAdminUser();
 491  
 492          // Create a course.
 493          $course = $this->getDataGenerator()->create_course();
 494  
 495          // Create a student.
 496          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 497  
 498          // Create a choice.
 499          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 500              'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
 501          $cm = get_coursemodule_from_instance('choice', $choice->id);
 502  
 503          $choicewithoptions = choice_get_choice($choice->id);
 504          $optionids = array_keys($choicewithoptions->option);
 505  
 506          choice_user_submit_response($optionids[0], $choice, $student->id, $course, $cm);
 507  
 508          // Create a calendar event.
 509          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
 510  
 511          // Create an action factory.
 512          $factory = new \core_calendar\action_factory();
 513  
 514          // Decorate action event for the student.
 515          $action = mod_choice_core_calendar_provide_event_action($event, $factory, $student->id);
 516  
 517          // Confirm no action was returned if the user has already submitted the
 518          // choice activity.
 519          $this->assertNull($action);
 520      }
 521  
 522      public function test_choice_core_calendar_provide_event_action_closed() {
 523          $this->resetAfterTest();
 524  
 525          $this->setAdminUser();
 526  
 527          // Create a course.
 528          $course = $this->getDataGenerator()->create_course();
 529  
 530          $timeclose = time() - DAYSECS;
 531          // Create a choice.
 532          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 533              'timeclose' => $timeclose));
 534  
 535          // Create a calendar event.
 536          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN, $timeclose - 1);
 537  
 538          // Create an action factory.
 539          $factory = new \core_calendar\action_factory();
 540  
 541          // Decorate action event.
 542          $action = mod_choice_core_calendar_provide_event_action($event, $factory);
 543  
 544          // Confirm not action was provided for a closed activity.
 545          $this->assertNull($action);
 546      }
 547  
 548      public function test_choice_core_calendar_provide_event_action_closed_for_user() {
 549          $this->resetAfterTest();
 550  
 551          $this->setAdminUser();
 552  
 553          // Create a course.
 554          $course = $this->getDataGenerator()->create_course();
 555  
 556          // Create a student.
 557          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 558  
 559          $timeclose = time() - DAYSECS;
 560          // Create a choice.
 561          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 562              'timeclose' => $timeclose));
 563  
 564          // Create a calendar event.
 565          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN, $timeclose - 1);
 566  
 567          // Create an action factory.
 568          $factory = new \core_calendar\action_factory();
 569  
 570          // Decorate action event for the student.
 571          $action = mod_choice_core_calendar_provide_event_action($event, $factory, $student->id);
 572  
 573          // Confirm not action was provided for a closed activity.
 574          $this->assertNull($action);
 575      }
 576  
 577      public function test_choice_core_calendar_provide_event_action_open_in_future() {
 578          $this->resetAfterTest();
 579  
 580          $this->setAdminUser();
 581  
 582          // Create a course.
 583          $course = $this->getDataGenerator()->create_course();
 584  
 585          $timeopen = time() + DAYSECS;
 586          $timeclose = $timeopen + DAYSECS;
 587  
 588          // Create a choice.
 589          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 590              'timeopen' => $timeopen, 'timeclose' => $timeclose));
 591  
 592          // Create a calendar event.
 593          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN, $timeopen);
 594  
 595          // Create an action factory.
 596          $factory = new \core_calendar\action_factory();
 597  
 598          // Decorate action event.
 599          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory);
 600  
 601          // Confirm the event was decorated.
 602          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 603          $this->assertEquals(get_string('viewchoices', 'choice'), $actionevent->get_name());
 604          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 605          $this->assertEquals(1, $actionevent->get_item_count());
 606          $this->assertFalse($actionevent->is_actionable());
 607      }
 608  
 609      public function test_choice_core_calendar_provide_event_action_open_in_future_for_user() {
 610          global $CFG;
 611  
 612          $this->resetAfterTest();
 613  
 614          $this->setAdminUser();
 615  
 616          // Create a course.
 617          $course = $this->getDataGenerator()->create_course();
 618  
 619          // Create a student.
 620          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 621  
 622          $timeopen = time() + DAYSECS;
 623          $timeclose = $timeopen + DAYSECS;
 624  
 625          // Create a choice.
 626          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
 627              'timeopen' => $timeopen, 'timeclose' => $timeclose));
 628  
 629          // Create a calendar event.
 630          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN, $timeopen);
 631  
 632          // Now, log out.
 633          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 634          $this->setUser();
 635  
 636          // Create an action factory.
 637          $factory = new \core_calendar\action_factory();
 638  
 639          // Decorate action event for the student.
 640          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory, $student->id);
 641  
 642          // Confirm the event was decorated.
 643          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 644          $this->assertEquals(get_string('viewchoices', 'choice'), $actionevent->get_name());
 645          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 646          $this->assertEquals(1, $actionevent->get_item_count());
 647          $this->assertFalse($actionevent->is_actionable());
 648      }
 649  
 650      public function test_choice_core_calendar_provide_event_action_no_time_specified() {
 651          $this->resetAfterTest();
 652  
 653          $this->setAdminUser();
 654  
 655          // Create a course.
 656          $course = $this->getDataGenerator()->create_course();
 657  
 658          // Create a choice.
 659          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
 660  
 661          // Create a calendar event.
 662          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
 663  
 664          // Create an action factory.
 665          $factory = new \core_calendar\action_factory();
 666  
 667          // Decorate action event.
 668          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory);
 669  
 670          // Confirm the event was decorated.
 671          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 672          $this->assertEquals(get_string('viewchoices', 'choice'), $actionevent->get_name());
 673          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 674          $this->assertEquals(1, $actionevent->get_item_count());
 675          $this->assertTrue($actionevent->is_actionable());
 676      }
 677  
 678      public function test_choice_core_calendar_provide_event_action_no_time_specified_for_user() {
 679          global $CFG;
 680  
 681          $this->resetAfterTest();
 682  
 683          $this->setAdminUser();
 684  
 685          // Create a course.
 686          $course = $this->getDataGenerator()->create_course();
 687  
 688          // Create a student.
 689          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 690  
 691          // Create a choice.
 692          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
 693  
 694          // Create a calendar event.
 695          $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
 696  
 697          // Now, log out.
 698          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 699          $this->setUser();
 700  
 701          // Create an action factory.
 702          $factory = new \core_calendar\action_factory();
 703  
 704          // Decorate action event for the student.
 705          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory, $student->id);
 706  
 707          // Confirm the event was decorated.
 708          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 709          $this->assertEquals(get_string('viewchoices', 'choice'), $actionevent->get_name());
 710          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 711          $this->assertEquals(1, $actionevent->get_item_count());
 712          $this->assertTrue($actionevent->is_actionable());
 713      }
 714  
 715      public function test_choice_core_calendar_provide_event_action_already_completed() {
 716          $this->resetAfterTest();
 717          set_config('enablecompletion', 1);
 718          $this->setAdminUser();
 719  
 720          // Create the activity.
 721          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 722          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id),
 723              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 724  
 725          // Get some additional data.
 726          $cm = get_coursemodule_from_instance('choice', $choice->id);
 727  
 728          // Create a calendar event.
 729          $event = $this->create_action_event($course->id, $choice->id,
 730              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 731  
 732          // Mark the activity as completed.
 733          $completion = new completion_info($course);
 734          $completion->set_module_viewed($cm);
 735  
 736          // Create an action factory.
 737          $factory = new \core_calendar\action_factory();
 738  
 739          // Decorate action event.
 740          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory);
 741  
 742          // Ensure result was null.
 743          $this->assertNull($actionevent);
 744      }
 745  
 746      public function test_choice_core_calendar_provide_event_action_already_completed_for_user() {
 747          $this->resetAfterTest();
 748          set_config('enablecompletion', 1);
 749          $this->setAdminUser();
 750  
 751          // Create the activity.
 752          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 753          $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id),
 754              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 755  
 756          // Enrol a student in the course.
 757          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 758  
 759          // Get some additional data.
 760          $cm = get_coursemodule_from_instance('choice', $choice->id);
 761  
 762          // Create a calendar event.
 763          $event = $this->create_action_event($course->id, $choice->id,
 764              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 765  
 766          // Mark the activity as completed for the student.
 767          $completion = new completion_info($course);
 768          $completion->set_module_viewed($cm, $student->id);
 769  
 770          // Create an action factory.
 771          $factory = new \core_calendar\action_factory();
 772  
 773          // Decorate action event for the student.
 774          $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory, $student->id);
 775  
 776          // Ensure result was null.
 777          $this->assertNull($actionevent);
 778      }
 779  
 780      /**
 781       * Creates an action event.
 782       *
 783       * @param int $courseid
 784       * @param int $instanceid The choice id.
 785       * @param string $eventtype The event type. eg. CHOICE_EVENT_TYPE_OPEN.
 786       * @param int|null $timestart The start timestamp for the event
 787       * @return bool|calendar_event
 788       */
 789      private function create_action_event($courseid, $instanceid, $eventtype, $timestart = null) {
 790          $event = new stdClass();
 791          $event->name = 'Calendar event';
 792          $event->modulename = 'choice';
 793          $event->courseid = $courseid;
 794          $event->instance = $instanceid;
 795          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 796          $event->eventtype = $eventtype;
 797  
 798          if ($timestart) {
 799              $event->timestart = $timestart;
 800          } else {
 801              $event->timestart = time();
 802          }
 803  
 804          return calendar_event::create($event);
 805      }
 806  
 807      /**
 808       * Test the callback responsible for returning the completion rule descriptions.
 809       * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
 810       * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
 811       */
 812      public function test_mod_choice_completion_get_active_rule_descriptions() {
 813          $this->resetAfterTest();
 814          $this->setAdminUser();
 815  
 816          // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
 817          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 818          $choice1 = $this->getDataGenerator()->create_module('choice', [
 819              'course' => $course->id,
 820              'completion' => 2,
 821              'completionsubmit' => 1
 822          ]);
 823          $choice2 = $this->getDataGenerator()->create_module('choice', [
 824              'course' => $course->id,
 825              'completion' => 2,
 826              'completionsubmit' => 0
 827          ]);
 828          $cm1 = cm_info::create(get_coursemodule_from_instance('choice', $choice1->id));
 829          $cm2 = cm_info::create(get_coursemodule_from_instance('choice', $choice2->id));
 830  
 831          // Data for the stdClass input type.
 832          // This type of input would occur when checking the default completion rules for an activity type, where we don't have
 833          // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
 834          $moddefaults = new stdClass();
 835          $moddefaults->customdata = ['customcompletionrules' => ['completionsubmit' => 1]];
 836          $moddefaults->completion = 2;
 837  
 838          $activeruledescriptions = [get_string('completionsubmit', 'choice')];
 839          $this->assertEquals(mod_choice_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
 840          $this->assertEquals(mod_choice_get_completion_active_rule_descriptions($cm2), []);
 841          $this->assertEquals(mod_choice_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
 842          $this->assertEquals(mod_choice_get_completion_active_rule_descriptions(new stdClass()), []);
 843      }
 844  
 845      /**
 846       * An unkown event type should not change the choice instance.
 847       */
 848      public function test_mod_choice_core_calendar_event_timestart_updated_unknown_event() {
 849          global $CFG, $DB;
 850          require_once($CFG->dirroot . "/calendar/lib.php");
 851  
 852          $this->resetAfterTest(true);
 853          $this->setAdminUser();
 854          $generator = $this->getDataGenerator();
 855          $course = $generator->create_course();
 856          $choicegenerator = $generator->get_plugin_generator('mod_choice');
 857          $timeopen = time();
 858          $timeclose = $timeopen + DAYSECS;
 859          $choice = $choicegenerator->create_instance(['course' => $course->id]);
 860          $choice->timeopen = $timeopen;
 861          $choice->timeclose = $timeclose;
 862          $DB->update_record('choice', $choice);
 863  
 864          // Create a valid event.
 865          $event = new \calendar_event([
 866              'name' => 'Test event',
 867              'description' => '',
 868              'format' => 1,
 869              'courseid' => $course->id,
 870              'groupid' => 0,
 871              'userid' => 2,
 872              'modulename' => 'choice',
 873              'instance' => $choice->id,
 874              'eventtype' => CHOICE_EVENT_TYPE_OPEN . "SOMETHING ELSE",
 875              'timestart' => 1,
 876              'timeduration' => 86400,
 877              'visible' => 1
 878          ]);
 879  
 880          mod_choice_core_calendar_event_timestart_updated($event, $choice);
 881  
 882          $choice = $DB->get_record('choice', ['id' => $choice->id]);
 883          $this->assertEquals($timeopen, $choice->timeopen);
 884          $this->assertEquals($timeclose, $choice->timeclose);
 885      }
 886  
 887      /**
 888       * A CHOICE_EVENT_TYPE_OPEN event should update the timeopen property of
 889       * the choice activity.
 890       */
 891      public function test_mod_choice_core_calendar_event_timestart_updated_open_event() {
 892          global $CFG, $DB;
 893          require_once($CFG->dirroot . "/calendar/lib.php");
 894  
 895          $this->resetAfterTest(true);
 896          $this->setAdminUser();
 897          $generator = $this->getDataGenerator();
 898          $course = $generator->create_course();
 899          $choicegenerator = $generator->get_plugin_generator('mod_choice');
 900          $timeopen = time();
 901          $timeclose = $timeopen + DAYSECS;
 902          $timemodified = 1;
 903          $newtimeopen = $timeopen - DAYSECS;
 904          $choice = $choicegenerator->create_instance(['course' => $course->id]);
 905          $choice->timeopen = $timeopen;
 906          $choice->timeclose = $timeclose;
 907          $choice->timemodified = $timemodified;
 908          $DB->update_record('choice', $choice);
 909  
 910          // Create a valid event.
 911          $event = new \calendar_event([
 912              'name' => 'Test event',
 913              'description' => '',
 914              'format' => 1,
 915              'courseid' => $course->id,
 916              'groupid' => 0,
 917              'userid' => 2,
 918              'modulename' => 'choice',
 919              'instance' => $choice->id,
 920              'eventtype' => CHOICE_EVENT_TYPE_OPEN,
 921              'timestart' => $newtimeopen,
 922              'timeduration' => 86400,
 923              'visible' => 1
 924          ]);
 925  
 926          // Trigger and capture the event when adding a contact.
 927          $sink = $this->redirectEvents();
 928  
 929          mod_choice_core_calendar_event_timestart_updated($event, $choice);
 930  
 931          $triggeredevents = $sink->get_events();
 932          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
 933              return is_a($e, 'core\event\course_module_updated');
 934          });
 935  
 936          $choice = $DB->get_record('choice', ['id' => $choice->id]);
 937          // Ensure the timeopen property matches the event timestart.
 938          $this->assertEquals($newtimeopen, $choice->timeopen);
 939          // Ensure the timeclose isn't changed.
 940          $this->assertEquals($timeclose, $choice->timeclose);
 941          // Ensure the timemodified property has been changed.
 942          $this->assertNotEquals($timemodified, $choice->timemodified);
 943          // Confirm that a module updated event is fired when the module
 944          // is changed.
 945          $this->assertNotEmpty($moduleupdatedevents);
 946      }
 947  
 948      /**
 949       * A CHOICE_EVENT_TYPE_CLOSE event should update the timeclose property of
 950       * the choice activity.
 951       */
 952      public function test_mod_choice_core_calendar_event_timestart_updated_close_event() {
 953          global $CFG, $DB;
 954          require_once($CFG->dirroot . "/calendar/lib.php");
 955  
 956          $this->resetAfterTest(true);
 957          $this->setAdminUser();
 958          $generator = $this->getDataGenerator();
 959          $course = $generator->create_course();
 960          $choicegenerator = $generator->get_plugin_generator('mod_choice');
 961          $timeopen = time();
 962          $timeclose = $timeopen + DAYSECS;
 963          $timemodified = 1;
 964          $newtimeclose = $timeclose + DAYSECS;
 965          $choice = $choicegenerator->create_instance(['course' => $course->id]);
 966          $choice->timeopen = $timeopen;
 967          $choice->timeclose = $timeclose;
 968          $choice->timemodified = $timemodified;
 969          $DB->update_record('choice', $choice);
 970  
 971          // Create a valid event.
 972          $event = new \calendar_event([
 973              'name' => 'Test event',
 974              'description' => '',
 975              'format' => 1,
 976              'courseid' => $course->id,
 977              'groupid' => 0,
 978              'userid' => 2,
 979              'modulename' => 'choice',
 980              'instance' => $choice->id,
 981              'eventtype' => CHOICE_EVENT_TYPE_CLOSE,
 982              'timestart' => $newtimeclose,
 983              'timeduration' => 86400,
 984              'visible' => 1
 985          ]);
 986  
 987          // Trigger and capture the event when adding a contact.
 988          $sink = $this->redirectEvents();
 989  
 990          mod_choice_core_calendar_event_timestart_updated($event, $choice);
 991  
 992          $triggeredevents = $sink->get_events();
 993          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
 994              return is_a($e, 'core\event\course_module_updated');
 995          });
 996  
 997          $choice = $DB->get_record('choice', ['id' => $choice->id]);
 998          // Ensure the timeclose property matches the event timestart.
 999          $this->assertEquals($newtimeclose, $choice->timeclose);
1000          // Ensure the timeopen isn't changed.
1001          $this->assertEquals($timeopen, $choice->timeopen);
1002          // Ensure the timemodified property has been changed.
1003          $this->assertNotEquals($timemodified, $choice->timemodified);
1004          // Confirm that a module updated event is fired when the module
1005          // is changed.
1006          $this->assertNotEmpty($moduleupdatedevents);
1007      }
1008  
1009      /**
1010       * An unkown event type should not have any limits
1011       */
1012      public function test_mod_choice_core_calendar_get_valid_event_timestart_range_unknown_event() {
1013          global $CFG, $DB;
1014          require_once($CFG->dirroot . "/calendar/lib.php");
1015  
1016          $this->resetAfterTest(true);
1017          $this->setAdminUser();
1018          $generator = $this->getDataGenerator();
1019          $course = $generator->create_course();
1020          $timeopen = time();
1021          $timeclose = $timeopen + DAYSECS;
1022          $choice = new \stdClass();
1023          $choice->timeopen = $timeopen;
1024          $choice->timeclose = $timeclose;
1025  
1026          // Create a valid event.
1027          $event = new \calendar_event([
1028              'name' => 'Test event',
1029              'description' => '',
1030              'format' => 1,
1031              'courseid' => $course->id,
1032              'groupid' => 0,
1033              'userid' => 2,
1034              'modulename' => 'choice',
1035              'instance' => 1,
1036              'eventtype' => CHOICE_EVENT_TYPE_OPEN . "SOMETHING ELSE",
1037              'timestart' => 1,
1038              'timeduration' => 86400,
1039              'visible' => 1
1040          ]);
1041  
1042          list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
1043          $this->assertNull($min);
1044          $this->assertNull($max);
1045      }
1046  
1047      /**
1048       * The open event should be limited by the choice's timeclose property, if it's set.
1049       */
1050      public function test_mod_choice_core_calendar_get_valid_event_timestart_range_open_event() {
1051          global $CFG, $DB;
1052          require_once($CFG->dirroot . "/calendar/lib.php");
1053  
1054          $this->resetAfterTest(true);
1055          $this->setAdminUser();
1056          $generator = $this->getDataGenerator();
1057          $course = $generator->create_course();
1058          $timeopen = time();
1059          $timeclose = $timeopen + DAYSECS;
1060          $choice = new \stdClass();
1061          $choice->timeopen = $timeopen;
1062          $choice->timeclose = $timeclose;
1063  
1064          // Create a valid event.
1065          $event = new \calendar_event([
1066              'name' => 'Test event',
1067              'description' => '',
1068              'format' => 1,
1069              'courseid' => $course->id,
1070              'groupid' => 0,
1071              'userid' => 2,
1072              'modulename' => 'choice',
1073              'instance' => 1,
1074              'eventtype' => CHOICE_EVENT_TYPE_OPEN,
1075              'timestart' => 1,
1076              'timeduration' => 86400,
1077              'visible' => 1
1078          ]);
1079  
1080          // The max limit should be bounded by the timeclose value.
1081          list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
1082  
1083          $this->assertNull($min);
1084          $this->assertEquals($timeclose, $max[0]);
1085  
1086          // No timeclose value should result in no upper limit.
1087          $choice->timeclose = 0;
1088          list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
1089  
1090          $this->assertNull($min);
1091          $this->assertNull($max);
1092      }
1093  
1094      /**
1095       * The close event should be limited by the choice's timeopen property, if it's set.
1096       */
1097      public function test_mod_choice_core_calendar_get_valid_event_timestart_range_close_event() {
1098          global $CFG, $DB;
1099          require_once($CFG->dirroot . "/calendar/lib.php");
1100  
1101          $this->resetAfterTest(true);
1102          $this->setAdminUser();
1103          $generator = $this->getDataGenerator();
1104          $course = $generator->create_course();
1105          $timeopen = time();
1106          $timeclose = $timeopen + DAYSECS;
1107          $choice = new \stdClass();
1108          $choice->timeopen = $timeopen;
1109          $choice->timeclose = $timeclose;
1110  
1111          // Create a valid event.
1112          $event = new \calendar_event([
1113              'name' => 'Test event',
1114              'description' => '',
1115              'format' => 1,
1116              'courseid' => $course->id,
1117              'groupid' => 0,
1118              'userid' => 2,
1119              'modulename' => 'choice',
1120              'instance' => 1,
1121              'eventtype' => CHOICE_EVENT_TYPE_CLOSE,
1122              'timestart' => 1,
1123              'timeduration' => 86400,
1124              'visible' => 1
1125          ]);
1126  
1127          // The max limit should be bounded by the timeclose value.
1128          list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
1129  
1130          $this->assertEquals($timeopen, $min[0]);
1131          $this->assertNull($max);
1132  
1133          // No timeclose value should result in no upper limit.
1134          $choice->timeopen = 0;
1135          list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
1136  
1137          $this->assertNull($min);
1138          $this->assertNull($max);
1139      }
1140  
1141      /**
1142       * Test choice_user_submit_response for a choice with specific options.
1143       * Options:
1144       * allowmultiple: false
1145       * limitanswers: false
1146       */
1147      public function test_choice_user_submit_response_no_multiple_no_limits() {
1148          global $DB;
1149          $this->resetAfterTest(true);
1150  
1151          $generator = $this->getDataGenerator();
1152          $course = $generator->create_course();
1153          $user = $generator->create_user();
1154          $user2 = $generator->create_user();
1155  
1156          // User must be enrolled in the course for choice limits to be honoured properly.
1157          $role = $DB->get_record('role', ['shortname' => 'student']);
1158          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
1159          $this->getDataGenerator()->enrol_user($user2->id, $course->id, $role->id);
1160  
1161          // Create choice, with updates allowed and a two options both limited to 1 response each.
1162          $choice = $generator->get_plugin_generator('mod_choice')->create_instance([
1163              'course' => $course->id,
1164              'allowupdate' => false,
1165              'limitanswers' => false,
1166              'allowmultiple' => false,
1167              'option' => ['red', 'green'],
1168          ]);
1169          $cm = get_coursemodule_from_instance('choice', $choice->id);
1170  
1171          // Get the choice, with options and limits included.
1172          $choicewithoptions = choice_get_choice($choice->id);
1173          $optionids = array_keys($choicewithoptions->option);
1174  
1175          // Now, save an response which includes the first option.
1176          $this->assertNull(choice_user_submit_response($optionids[0], $choicewithoptions, $user->id, $course, $cm));
1177  
1178          // Confirm that saving again without changing the selected option will not throw a 'choice full' exception.
1179          $this->assertNull(choice_user_submit_response($optionids[1], $choicewithoptions, $user->id, $course, $cm));
1180  
1181          // Confirm that saving a response for student 2 including the first option is allowed.
1182          $this->assertNull(choice_user_submit_response($optionids[0], $choicewithoptions, $user2->id, $course, $cm));
1183  
1184          // Confirm that trying to save multiple options results in an exception.
1185          $this->expectException('moodle_exception');
1186          choice_user_submit_response([$optionids[1], $optionids[1]], $choicewithoptions, $user->id, $course, $cm);
1187      }
1188  
1189      /**
1190       * Test choice_user_submit_response for a choice with specific options.
1191       * Options:
1192       * allowmultiple: true
1193       * limitanswers: false
1194       */
1195      public function test_choice_user_submit_response_multiples_no_limits() {
1196          global $DB;
1197          $this->resetAfterTest(true);
1198  
1199          $generator = $this->getDataGenerator();
1200          $course = $generator->create_course();
1201          $user = $generator->create_user();
1202          $user2 = $generator->create_user();
1203  
1204          // User must be enrolled in the course for choice limits to be honoured properly.
1205          $role = $DB->get_record('role', ['shortname' => 'student']);
1206          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
1207          $this->getDataGenerator()->enrol_user($user2->id, $course->id, $role->id);
1208  
1209          // Create choice, with updates allowed and a two options both limited to 1 response each.
1210          $choice = $generator->get_plugin_generator('mod_choice')->create_instance([
1211              'course' => $course->id,
1212              'allowupdate' => false,
1213              'allowmultiple' => true,
1214              'limitanswers' => false,
1215              'option' => ['red', 'green'],
1216          ]);
1217          $cm = get_coursemodule_from_instance('choice', $choice->id);
1218  
1219          // Get the choice, with options and limits included.
1220          $choicewithoptions = choice_get_choice($choice->id);
1221          $optionids = array_keys($choicewithoptions->option);
1222  
1223          // Save a response which includes the first option only.
1224          $this->assertNull(choice_user_submit_response([$optionids[0]], $choicewithoptions, $user->id, $course, $cm));
1225  
1226          // Confirm that adding an option to the response is allowed.
1227          $this->assertNull(choice_user_submit_response([$optionids[0], $optionids[1]], $choicewithoptions, $user->id, $course, $cm));
1228  
1229          // Confirm that saving a response for student 2 including the first option is allowed.
1230          $this->assertNull(choice_user_submit_response($optionids[0], $choicewithoptions, $user2->id, $course, $cm));
1231  
1232          // Confirm that removing an option from the response is allowed.
1233          $this->assertNull(choice_user_submit_response([$optionids[0]], $choicewithoptions, $user->id, $course, $cm));
1234  
1235          // Confirm that removing all options from the response is not allowed via this method.
1236          $this->expectException('moodle_exception');
1237          choice_user_submit_response([], $choicewithoptions, $user->id, $course, $cm);
1238      }
1239  
1240      /**
1241       * Test choice_user_submit_response for a choice with specific options.
1242       * Options:
1243       * allowmultiple: false
1244       * limitanswers: true
1245       */
1246      public function test_choice_user_submit_response_no_multiples_limits() {
1247          global $DB;
1248          $this->resetAfterTest(true);
1249  
1250          $generator = $this->getDataGenerator();
1251          $course = $generator->create_course();
1252          $user = $generator->create_user();
1253          $user2 = $generator->create_user();
1254  
1255          // User must be enrolled in the course for choice limits to be honoured properly.
1256          $role = $DB->get_record('role', ['shortname' => 'student']);
1257          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
1258          $this->getDataGenerator()->enrol_user($user2->id, $course->id, $role->id);
1259  
1260          // Create choice, with updates allowed and a two options both limited to 1 response each.
1261          $choice = $generator->get_plugin_generator('mod_choice')->create_instance([
1262              'course' => $course->id,
1263              'allowupdate' => false,
1264              'allowmultiple' => false,
1265              'limitanswers' => true,
1266              'option' => ['red', 'green'],
1267              'limit' => [1, 1]
1268          ]);
1269          $cm = get_coursemodule_from_instance('choice', $choice->id);
1270  
1271          // Get the choice, with options and limits included.
1272          $choicewithoptions = choice_get_choice($choice->id);
1273          $optionids = array_keys($choicewithoptions->option);
1274  
1275          // Save a response which includes the first option only.
1276          $this->assertNull(choice_user_submit_response($optionids[0], $choicewithoptions, $user->id, $course, $cm));
1277  
1278          // Confirm that changing the option in the response is allowed.
1279          $this->assertNull(choice_user_submit_response($optionids[1], $choicewithoptions, $user->id, $course, $cm));
1280  
1281          // Confirm that limits are respected by trying to save the same option as another user.
1282          $this->expectException('moodle_exception');
1283          choice_user_submit_response($optionids[1], $choicewithoptions, $user2->id, $course, $cm);
1284      }
1285  
1286      /**
1287       * Test choice_user_submit_response for a choice with specific options.
1288       * Options:
1289       * allowmultiple: true
1290       * limitanswers: true
1291       */
1292      public function test_choice_user_submit_response_multiples_limits() {
1293          global $DB;
1294          $this->resetAfterTest(true);
1295  
1296          $generator = $this->getDataGenerator();
1297          $course = $generator->create_course();
1298          $user = $generator->create_user();
1299          $user2 = $generator->create_user();
1300  
1301          // User must be enrolled in the course for choice limits to be honoured properly.
1302          $role = $DB->get_record('role', ['shortname' => 'student']);
1303          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
1304          $this->getDataGenerator()->enrol_user($user2->id, $course->id, $role->id);
1305  
1306          // Create choice, with updates allowed and a two options both limited to 1 response each.
1307          $choice = $generator->get_plugin_generator('mod_choice')->create_instance([
1308              'course' => $course->id,
1309              'allowupdate' => false,
1310              'allowmultiple' => true,
1311              'limitanswers' => true,
1312              'option' => ['red', 'green'],
1313              'limit' => [1, 1]
1314          ]);
1315          $cm = get_coursemodule_from_instance('choice', $choice->id);
1316  
1317          // Get the choice, with options and limits included.
1318          $choicewithoptions = choice_get_choice($choice->id);
1319          $optionids = array_keys($choicewithoptions->option);
1320  
1321          // Now, save a response which includes the first option only.
1322          $this->assertNull(choice_user_submit_response([$optionids[0]], $choicewithoptions, $user->id, $course, $cm));
1323  
1324          // Confirm that changing the option in the response is allowed.
1325          $this->assertNull(choice_user_submit_response([$optionids[1]], $choicewithoptions, $user->id, $course, $cm));
1326  
1327          // Confirm that adding an option to the response is allowed.
1328          $this->assertNull(choice_user_submit_response([$optionids[0], $optionids[1]], $choicewithoptions, $user->id, $course, $cm));
1329  
1330          // Confirm that limits are respected by trying to save the same option as another user.
1331          $this->expectException('moodle_exception');
1332          choice_user_submit_response($optionids[1], $choicewithoptions, $user2->id, $course, $cm);
1333      }
1334  
1335      /**
1336       * A user who does not have capabilities to add events to the calendar should be able to create an choice.
1337       */
1338      public function test_creation_with_no_calendar_capabilities() {
1339          $this->resetAfterTest();
1340          $course = self::getDataGenerator()->create_course();
1341          $context = context_course::instance($course->id);
1342          $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
1343          $roleid = self::getDataGenerator()->create_role();
1344          self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
1345          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
1346          $generator = self::getDataGenerator()->get_plugin_generator('mod_choice');
1347          // Create an instance as a user without the calendar capabilities.
1348          $this->setUser($user);
1349          $time = time();
1350          $params = array(
1351              'course' => $course->id,
1352              'timeopen' => $time + 200,
1353              'timeclose' => $time + 500,
1354          );
1355          $generator->create_instance($params);
1356      }
1357  }