Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

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