Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

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