Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   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   * Unit tests for (some of) mod/assign/lib.php.
  19   *
  20   * @package    mod_assign
  21   * @category   phpunit
  22   * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  namespace mod_assign;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  global $CFG;
  30  require_once($CFG->dirroot . '/mod/assign/lib.php');
  31  require_once($CFG->dirroot . '/mod/assign/locallib.php');
  32  require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
  33  
  34  use core_calendar\local\api as calendar_local_api;
  35  use core_calendar\local\event\container as calendar_event_container;
  36  use mod_assign_test_generator;
  37  
  38  /**
  39   * Unit tests for (some of) mod/assign/lib.php.
  40   *
  41   * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  42   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  43   */
  44  class lib_test extends \advanced_testcase {
  45  
  46      // Use the generator helper.
  47      use mod_assign_test_generator;
  48  
  49      /**
  50       * Test that assign_print_recent_activity shows ungraded submitted assignments.
  51       */
  52      public function test_print_recent_activity() {
  53          $this->resetAfterTest();
  54          $course = $this->getDataGenerator()->create_course();
  55          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
  56          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
  57          $assign = $this->create_instance($course);
  58          $this->submit_for_grading($student, $assign);
  59  
  60          $this->setUser($teacher);
  61          $this->expectOutputRegex('/submitted:/');
  62          assign_print_recent_activity($course, true, time() - 3600);
  63      }
  64  
  65      /**
  66       * Test that assign_print_recent_activity does not display any warnings when a custom fullname has been configured.
  67       */
  68      public function test_print_recent_activity_fullname() {
  69          $this->resetAfterTest();
  70          $course = $this->getDataGenerator()->create_course();
  71          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
  72          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
  73          $assign = $this->create_instance($course);
  74          $this->submit_for_grading($student, $assign);
  75  
  76          $this->setUser($teacher);
  77          $this->expectOutputRegex('/submitted:/');
  78          set_config('fullnamedisplay', 'firstname, lastnamephonetic');
  79          assign_print_recent_activity($course, false, time() - 3600);
  80      }
  81  
  82      /**
  83       * Test that assign_print_recent_activity shows the blind marking ID.
  84       */
  85      public function test_print_recent_activity_fullname_blind_marking() {
  86          $this->resetAfterTest();
  87          $course = $this->getDataGenerator()->create_course();
  88          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
  89          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
  90  
  91          $assign = $this->create_instance($course, [
  92                  'blindmarking' => 1,
  93              ]);
  94          $this->add_submission($student, $assign);
  95          $this->submit_for_grading($student, $assign);
  96  
  97          $this->setUser($teacher);
  98          $uniqueid = $assign->get_uniqueid_for_user($student->id);
  99          $expectedstr = preg_quote(get_string('participant', 'mod_assign'), '/') . '.*' . $uniqueid;
 100          $this->expectOutputRegex("/{$expectedstr}/");
 101          assign_print_recent_activity($course, false, time() - 3600);
 102      }
 103  
 104      /**
 105       * Test that assign_get_recent_mod_activity fetches the assignment correctly.
 106       */
 107      public function test_assign_get_recent_mod_activity() {
 108          $this->resetAfterTest();
 109          $course = $this->getDataGenerator()->create_course();
 110          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 111          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 112          $assign = $this->create_instance($course);
 113          $this->add_submission($student, $assign);
 114          $this->submit_for_grading($student, $assign);
 115  
 116          $index = 1;
 117          $activities = [
 118              $index => (object) [
 119                  'type' => 'assign',
 120                  'cmid' => $assign->get_course_module()->id,
 121              ],
 122          ];
 123  
 124          $this->setUser($teacher);
 125          assign_get_recent_mod_activity($activities, $index, time() - HOURSECS, $course->id, $assign->get_course_module()->id);
 126  
 127          $activity = $activities[1];
 128          $this->assertEquals("assign", $activity->type);
 129          $this->assertEquals($student->id, $activity->user->id);
 130      }
 131  
 132      /**
 133       * Ensure that assign_user_complete displays information about drafts.
 134       */
 135      public function test_assign_user_complete() {
 136          global $PAGE, $DB;
 137  
 138          $this->resetAfterTest();
 139          $course = $this->getDataGenerator()->create_course();
 140          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 141          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 142          $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
 143          $this->add_submission($student, $assign);
 144  
 145          $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
 146  
 147          $submission = $assign->get_user_submission($student->id, true);
 148          $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
 149          $DB->update_record('assign_submission', $submission);
 150  
 151          $this->expectOutputRegex('/Draft/');
 152          assign_user_complete($course, $student, $assign->get_course_module(), $assign->get_instance());
 153      }
 154  
 155      /**
 156       * Ensure that assign_user_outline fetches updated grades.
 157       */
 158      public function test_assign_user_outline() {
 159          $this->resetAfterTest();
 160          $course = $this->getDataGenerator()->create_course();
 161          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 162          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 163          $assign = $this->create_instance($course);
 164  
 165          $this->add_submission($student, $assign);
 166          $this->submit_for_grading($student, $assign);
 167          $this->mark_submission($teacher, $assign, $student, 50.0);
 168  
 169          $this->setUser($teacher);
 170          $data = $assign->get_user_grade($student->id, true);
 171          $data->grade = '50.5';
 172          $assign->update_grade($data);
 173  
 174          $result = assign_user_outline($course, $student, $assign->get_course_module(), $assign->get_instance());
 175  
 176          $this->assertMatchesRegularExpression('/50.5/', $result->info);
 177      }
 178  
 179      /**
 180       * Ensure that assign_get_completion_state reflects the correct status at each point.
 181       */
 182      public function test_assign_get_completion_state() {
 183          global $DB;
 184  
 185          $this->resetAfterTest();
 186          $course = $this->getDataGenerator()->create_course();
 187          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 188          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 189          $assign = $this->create_instance($course, [
 190                  'submissiondrafts' => 0,
 191                  'completionsubmit' => 1
 192              ]);
 193  
 194          $this->setUser($student);
 195          $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
 196          $this->assertFalse($result);
 197  
 198          $this->add_submission($student, $assign);
 199          $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
 200          $this->assertFalse($result);
 201  
 202          $this->submit_for_grading($student, $assign);
 203          $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
 204          $this->assertTrue($result);
 205  
 206          $this->mark_submission($teacher, $assign, $student, 50.0);
 207          $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
 208          $this->assertTrue($result);
 209      }
 210  
 211      /**
 212       * Tests for mod_assign_refresh_events.
 213       */
 214      public function test_assign_refresh_events() {
 215          global $DB;
 216  
 217          $this->resetAfterTest();
 218  
 219          $duedate = time();
 220          $newduedate = $duedate + DAYSECS;
 221  
 222          $this->setAdminUser();
 223  
 224          $course = $this->getDataGenerator()->create_course();
 225          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 226          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 227          $assign = $this->create_instance($course, [
 228                  'duedate' => $duedate,
 229              ]);
 230  
 231          $instance = $assign->get_instance();
 232          $eventparams = [
 233              'modulename' => 'assign',
 234              'instance' => $instance->id,
 235              'eventtype' => ASSIGN_EVENT_TYPE_DUE,
 236              'groupid' => 0
 237          ];
 238  
 239          // Make sure the calendar event for assignment 1 matches the initial due date.
 240          $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
 241          $this->assertEquals($eventtime, $duedate);
 242  
 243          // Manually update assignment 1's due date.
 244          $DB->update_record('assign', (object) [
 245              'id' => $instance->id,
 246              'duedate' => $newduedate,
 247              'course' => $course->id
 248          ]);
 249  
 250          // Then refresh the assignment events of assignment 1's course.
 251          $this->assertTrue(assign_refresh_events($course->id));
 252  
 253          // Confirm that the assignment 1's due date event now has the new due date after refresh.
 254          $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
 255          $this->assertEquals($eventtime, $newduedate);
 256  
 257          // Create a second course and assignment.
 258          $othercourse = $this->getDataGenerator()->create_course();;
 259          $otherassign = $this->create_instance($othercourse, [
 260              'duedate' => $duedate,
 261          ]);
 262          $otherinstance = $otherassign->get_instance();
 263  
 264          // Manually update assignment 1 and 2's due dates.
 265          $newduedate += DAYSECS;
 266          $DB->update_record('assign', (object)[
 267              'id' => $instance->id,
 268              'duedate' => $newduedate,
 269              'course' => $course->id
 270          ]);
 271          $DB->update_record('assign', (object)[
 272              'id' => $otherinstance->id,
 273              'duedate' => $newduedate,
 274              'course' => $othercourse->id
 275          ]);
 276  
 277          // Refresh events of all courses and check the calendar events matches the new date.
 278          $this->assertTrue(assign_refresh_events());
 279  
 280          // Check the due date calendar event for assignment 1.
 281          $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
 282          $this->assertEquals($eventtime, $newduedate);
 283  
 284          // Check the due date calendar event for assignment 2.
 285          $eventparams['instance'] = $otherinstance->id;
 286          $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
 287          $this->assertEquals($eventtime, $newduedate);
 288  
 289          // In case the course ID is passed as a numeric string.
 290          $this->assertTrue(assign_refresh_events('' . $course->id));
 291  
 292          // Non-existing course ID.
 293          $this->assertFalse(assign_refresh_events(-1));
 294  
 295          // Invalid course ID.
 296          $this->assertFalse(assign_refresh_events('aaa'));
 297      }
 298  
 299      public function test_assign_core_calendar_is_event_visible_duedate_event_as_teacher() {
 300          $this->resetAfterTest();
 301          $course = $this->getDataGenerator()->create_course();
 302          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 303          $assign = $this->create_instance($course);
 304  
 305          $this->setAdminUser();
 306  
 307          // Create a calendar event.
 308          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 309  
 310          // The teacher should see the due date event.
 311          $this->setUser($teacher);
 312          $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
 313      }
 314  
 315      public function test_assign_core_calendar_is_event_visible_duedate_event_for_teacher() {
 316          $this->resetAfterTest();
 317          $course = $this->getDataGenerator()->create_course();
 318          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 319          $assign = $this->create_instance($course);
 320  
 321          $this->setAdminUser();
 322  
 323          // Create a calendar event.
 324          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 325  
 326          // Now, log out.
 327          $this->setUser();
 328  
 329          // The teacher should see the due date event.
 330          $this->assertTrue(mod_assign_core_calendar_is_event_visible($event, $teacher->id));
 331      }
 332  
 333      public function test_assign_core_calendar_is_event_visible_duedate_event_as_student() {
 334          $this->resetAfterTest();
 335          $course = $this->getDataGenerator()->create_course();
 336          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 337          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 338          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
 339  
 340          $this->setAdminUser();
 341  
 342          // Create a calendar event.
 343          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 344  
 345          // The student should care about the due date event.
 346          $this->setUser($student);
 347          $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
 348      }
 349  
 350      public function test_assign_core_calendar_is_event_visible_duedate_event_for_student() {
 351          $this->resetAfterTest();
 352          $course = $this->getDataGenerator()->create_course();
 353          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 354          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 355          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
 356  
 357          $this->setAdminUser();
 358  
 359          // Create a calendar event.
 360          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 361  
 362          // Now, log out.
 363          $this->setUser();
 364  
 365          // The student should care about the due date event.
 366          $this->assertTrue(mod_assign_core_calendar_is_event_visible($event, $student->id));
 367      }
 368  
 369      public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_teacher() {
 370          $this->resetAfterTest();
 371          $course = $this->getDataGenerator()->create_course();
 372          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 373          $assign = $this->create_instance($course);
 374  
 375          // Create a calendar event.
 376          $this->setAdminUser();
 377          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 378  
 379          // The teacher should see the due date event.
 380          $this->setUser($teacher);
 381          $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
 382      }
 383  
 384  
 385      public function test_assign_core_calendar_is_event_visible_gradingduedate_event_for_teacher() {
 386          $this->resetAfterTest();
 387          $course = $this->getDataGenerator()->create_course();
 388          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 389          $assign = $this->create_instance($course);
 390  
 391          // Create a calendar event.
 392          $this->setAdminUser();
 393          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 394  
 395          // Now, log out.
 396          $this->setUser();
 397  
 398          // The teacher should see the due date event.
 399          $this->assertTrue(mod_assign_core_calendar_is_event_visible($event, $teacher->id));
 400      }
 401  
 402      public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_student() {
 403          $this->resetAfterTest();
 404          $course = $this->getDataGenerator()->create_course();
 405          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 406          $assign = $this->create_instance($course);
 407  
 408          // Create a calendar event.
 409          $this->setAdminUser();
 410          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 411  
 412          // The student should not see the due date event.
 413          $this->setUser($student);
 414          $this->assertFalse(mod_assign_core_calendar_is_event_visible($event));
 415      }
 416  
 417  
 418      public function test_assign_core_calendar_is_event_visible_gradingduedate_event_for_student() {
 419          $this->resetAfterTest();
 420          $course = $this->getDataGenerator()->create_course();
 421          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 422          $assign = $this->create_instance($course);
 423  
 424          // Create a calendar event.
 425          $this->setAdminUser();
 426          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 427  
 428          // Now, log out.
 429          $this->setUser();
 430  
 431          // The student should not see the due date event.
 432          $this->assertFalse(mod_assign_core_calendar_is_event_visible($event, $student->id));
 433      }
 434  
 435      public function test_assign_core_calendar_provide_event_action_duedate_as_teacher() {
 436          $this->resetAfterTest();
 437          $course = $this->getDataGenerator()->create_course();
 438          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 439          $assign = $this->create_instance($course);
 440  
 441          // Create a calendar event.
 442          $this->setAdminUser();
 443          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 444  
 445          // The teacher should see the event.
 446          $this->setUser($teacher);
 447          $factory = new \core_calendar\action_factory();
 448          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 449  
 450          // The teacher should not have an action for a due date event.
 451          $this->assertNull($actionevent);
 452      }
 453  
 454      public function test_assign_core_calendar_provide_event_action_duedate_for_teacher() {
 455          $this->resetAfterTest();
 456          $course = $this->getDataGenerator()->create_course();
 457          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 458          $assign = $this->create_instance($course);
 459  
 460          // Create a calendar event.
 461          $this->setAdminUser();
 462          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 463  
 464          // Now, log out.
 465          $this->setUser();
 466  
 467          // Decorate action event for a teacher.
 468          $factory = new \core_calendar\action_factory();
 469          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $teacher->id);
 470  
 471          // The teacher should not have an action for a due date event.
 472          $this->assertNull($actionevent);
 473      }
 474  
 475      public function test_assign_core_calendar_provide_event_action_duedate_as_student() {
 476          $this->resetAfterTest();
 477          $course = $this->getDataGenerator()->create_course();
 478          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 479          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
 480  
 481          // Create a calendar event.
 482          $this->setAdminUser();
 483          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 484  
 485          // The student should see the event.
 486          $this->setUser($student);
 487          $factory = new \core_calendar\action_factory();
 488          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 489  
 490          // Confirm the event was decorated.
 491          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 492          $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent->get_name());
 493          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 494          $this->assertEquals(1, $actionevent->get_item_count());
 495          $this->assertTrue($actionevent->is_actionable());
 496      }
 497  
 498      public function test_assign_core_calendar_provide_event_action_duedate_for_student() {
 499          $this->resetAfterTest();
 500          $course = $this->getDataGenerator()->create_course();
 501          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 502          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
 503  
 504          // Create a calendar event.
 505          $this->setAdminUser();
 506          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 507  
 508          // Now, log out.
 509          $this->setUser();
 510  
 511          // Decorate action event for a student.
 512          $factory = new \core_calendar\action_factory();
 513          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
 514  
 515          // Confirm the event was decorated.
 516          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 517          $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent->get_name());
 518          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 519          $this->assertEquals(1, $actionevent->get_item_count());
 520          $this->assertTrue($actionevent->is_actionable());
 521      }
 522  
 523      public function test_assign_core_calendar_provide_event_action_gradingduedate_as_teacher() {
 524          $this->resetAfterTest();
 525          $course = $this->getDataGenerator()->create_course();
 526          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 527          $assign = $this->create_instance($course);
 528  
 529          // Create a calendar event.
 530          $this->setAdminUser();
 531          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 532  
 533          $this->setUser($teacher);
 534          $factory = new \core_calendar\action_factory();
 535          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 536  
 537          // Confirm the event was decorated.
 538          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 539          $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
 540          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 541          $this->assertEquals(0, $actionevent->get_item_count());
 542          $this->assertTrue($actionevent->is_actionable());
 543      }
 544  
 545      public function test_assign_core_calendar_provide_event_action_gradingduedate_for_teacher() {
 546          $this->resetAfterTest();
 547          $course = $this->getDataGenerator()->create_course();
 548          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 549          $assign = $this->create_instance($course);
 550  
 551          // Create a calendar event.
 552          $this->setAdminUser();
 553          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 554  
 555          // Now, log out.
 556          $this->setUser();
 557  
 558          // Decorate action event for a teacher.
 559          $factory = new \core_calendar\action_factory();
 560          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $teacher->id);
 561  
 562          // Confirm the event was decorated.
 563          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 564          $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
 565          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 566          $this->assertEquals(0, $actionevent->get_item_count());
 567          $this->assertTrue($actionevent->is_actionable());
 568      }
 569  
 570      public function test_assign_core_calendar_provide_event_action_gradingduedate_as_student() {
 571          $this->resetAfterTest();
 572          $course = $this->getDataGenerator()->create_course();
 573          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 574          $assign = $this->create_instance($course);
 575  
 576          // Create a calendar event.
 577          $this->setAdminUser();
 578          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 579  
 580          $this->setUser($student);
 581          $factory = new \core_calendar\action_factory();
 582          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 583  
 584          // Confirm the event was decorated.
 585          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 586          $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
 587          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 588          $this->assertEquals(0, $actionevent->get_item_count());
 589          $this->assertFalse($actionevent->is_actionable());
 590      }
 591  
 592      public function test_assign_core_calendar_provide_event_action_gradingduedate_for_student() {
 593          $this->resetAfterTest();
 594          $course = $this->getDataGenerator()->create_course();
 595          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 596          $assign = $this->create_instance($course);
 597  
 598          // Create a calendar event.
 599          $this->setAdminUser();
 600          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 601  
 602          // Now, log out.
 603          $this->setUser();
 604  
 605          // Decorate action event for a student.
 606          $factory = new \core_calendar\action_factory();
 607          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
 608  
 609          // Confirm the event was decorated.
 610          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 611          $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
 612          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 613          $this->assertEquals(0, $actionevent->get_item_count());
 614          $this->assertFalse($actionevent->is_actionable());
 615      }
 616  
 617      public function test_assign_core_calendar_provide_event_action_duedate_as_student_submitted() {
 618          $this->resetAfterTest();
 619          $course = $this->getDataGenerator()->create_course();
 620          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 621          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 622          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
 623  
 624          $this->setAdminUser();
 625  
 626          // Create a calendar event.
 627          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 628  
 629          // Create an action factory.
 630          $factory = new \core_calendar\action_factory();
 631  
 632          // Submit as the student.
 633          $this->add_submission($student, $assign);
 634          $this->submit_for_grading($student, $assign);
 635  
 636          // Confirm there was no event to action.
 637          $factory = new \core_calendar\action_factory();
 638          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 639          $this->assertNull($actionevent);
 640      }
 641  
 642      public function test_assign_core_calendar_provide_event_action_duedate_for_student_submitted() {
 643          $this->resetAfterTest();
 644          $course = $this->getDataGenerator()->create_course();
 645          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 646          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 647          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
 648  
 649          $this->setAdminUser();
 650  
 651          // Create a calendar event.
 652          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 653  
 654          // Create an action factory.
 655          $factory = new \core_calendar\action_factory();
 656  
 657          // Submit as the student.
 658          $this->add_submission($student, $assign);
 659          $this->submit_for_grading($student, $assign);
 660  
 661          // Now, log out.
 662          $this->setUser();
 663  
 664          // Confirm there was no event to action.
 665          $factory = new \core_calendar\action_factory();
 666          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
 667          $this->assertNull($actionevent);
 668      }
 669  
 670      public function test_assign_core_calendar_provide_event_action_already_completed() {
 671          $this->resetAfterTest();
 672          set_config('enablecompletion', 1);
 673          $this->setAdminUser();
 674  
 675          // Create the activity.
 676          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 677          $assign = $this->create_instance($course,
 678           ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]);
 679  
 680          // Get some additional data.
 681          $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
 682  
 683          // Create a calendar event.
 684          $event = $this->create_action_event($course, $assign,
 685              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 686  
 687          // Mark the activity as completed.
 688          $completion = new \completion_info($course);
 689          $completion->set_module_viewed($cm);
 690  
 691          // Create an action factory.
 692          $factory = new \core_calendar\action_factory();
 693  
 694          // Decorate action event.
 695          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 696  
 697          // Ensure result was null.
 698          $this->assertNull($actionevent);
 699      }
 700  
 701      public function test_assign_core_calendar_provide_event_action_already_completed_for_user() {
 702          $this->resetAfterTest();
 703          set_config('enablecompletion', 1);
 704          $this->setAdminUser();
 705  
 706          // Create the activity.
 707          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 708          $assign = $this->create_instance($course,
 709           ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]);
 710  
 711          // Enrol a student in the course.
 712          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 713  
 714          // Get some additional data.
 715          $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
 716  
 717          // Create a calendar event.
 718          $event = $this->create_action_event($course, $assign,
 719              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 720  
 721          // Mark the activity as completed for the student.
 722          $completion = new \completion_info($course);
 723          $completion->set_module_viewed($cm, $student->id);
 724  
 725          // Create an action factory.
 726          $factory = new \core_calendar\action_factory();
 727  
 728          // Decorate action event for the student.
 729          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
 730  
 731          // Ensure result was null.
 732          $this->assertNull($actionevent);
 733      }
 734  
 735      /**
 736       * Creates an action event.
 737       *
 738       * @param \stdClass $course The course the assignment is in
 739       * @param assign $assign The assignment to create an event for
 740       * @param string $eventtype The event type. eg. ASSIGN_EVENT_TYPE_DUE.
 741       * @return bool|calendar_event
 742       */
 743      private function create_action_event($course, $assign, $eventtype) {
 744          $event = new \stdClass();
 745          $event->name = 'Calendar event';
 746          $event->modulename  = 'assign';
 747          $event->courseid = $course->id;
 748          $event->instance = $assign->get_instance()->id;
 749          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 750          $event->eventtype = $eventtype;
 751          $event->timestart = time();
 752  
 753          return \calendar_event::create($event);
 754      }
 755  
 756      /**
 757       * Test the callback responsible for returning the completion rule descriptions.
 758       * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
 759       * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
 760       */
 761      public function test_mod_assign_completion_get_active_rule_descriptions() {
 762          $this->resetAfterTest();
 763          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 764  
 765          $this->setAdminUser();
 766  
 767          // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
 768          $cm1 = $this->create_instance($course, ['completion' => '2', 'completionsubmit' => '1'])->get_course_module();
 769          $cm2 = $this->create_instance($course, ['completion' => '2', 'completionsubmit' => '0'])->get_course_module();
 770  
 771          // Data for the stdClass input type.
 772          // This type of input would occur when checking the default completion rules for an activity type, where we don't have
 773          // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
 774          $moddefaults = (object) [
 775              'customdata' => [
 776                  'customcompletionrules' => [
 777                      'completionsubmit' => '1',
 778                  ],
 779              ],
 780              'completion' => 2,
 781          ];
 782  
 783          $activeruledescriptions = [get_string('completionsubmit', 'assign')];
 784          $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
 785          $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($cm2), []);
 786          $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
 787          $this->assertEquals(mod_assign_get_completion_active_rule_descriptions(new \stdClass()), []);
 788      }
 789  
 790      /**
 791       * Test that if some grades are not set, they are left alone and not rescaled
 792       */
 793      public function test_assign_rescale_activity_grades_some_unset() {
 794          $this->resetAfterTest();
 795          $course = $this->getDataGenerator()->create_course();
 796          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 797          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 798          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
 799  
 800          // As a teacher.
 801          $this->setUser($teacher);
 802          $assign = $this->create_instance($course);
 803  
 804          // Grade the student.
 805          $data = ['grade' => 50];
 806          $assign->testable_apply_grade_to_user((object)$data, $student->id, 0);
 807  
 808          // Try getting another students grade. This will give a grade of ASSIGN_GRADE_NOT_SET (-1).
 809          $assign->get_user_grade($otherstudent->id, true);
 810  
 811          // Rescale.
 812          assign_rescale_activity_grades($course, $assign->get_course_module(), 0, 100, 0, 10);
 813  
 814          // Get the grades for both students.
 815          $studentgrade = $assign->get_user_grade($student->id, true);
 816          $otherstudentgrade = $assign->get_user_grade($otherstudent->id, true);
 817  
 818          // Make sure the real grade is scaled, but the ASSIGN_GRADE_NOT_SET stays the same.
 819          $this->assertEquals($studentgrade->grade, 5);
 820          $this->assertEquals($otherstudentgrade->grade, ASSIGN_GRADE_NOT_SET);
 821      }
 822  
 823      /**
 824       * Return false when there are not overrides for this assign instance.
 825       */
 826      public function test_assign_is_override_calendar_event_no_override() {
 827          global $CFG, $DB;
 828          require_once($CFG->dirroot . '/calendar/lib.php');
 829  
 830          $this->resetAfterTest();
 831          $course = $this->getDataGenerator()->create_course();
 832          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 833  
 834          $this->setAdminUser();
 835  
 836          $duedate = time();
 837          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 838  
 839          $instance = $assign->get_instance();
 840          $event = new \calendar_event((object)[
 841              'modulename' => 'assign',
 842              'instance' => $instance->id,
 843              'userid' => $student->id,
 844          ]);
 845  
 846          $this->assertFalse($assign->is_override_calendar_event($event));
 847      }
 848  
 849      /**
 850       * Return false if the given event isn't an assign module event.
 851       */
 852      public function test_assign_is_override_calendar_event_no_nodule_event() {
 853          global $CFG, $DB;
 854          require_once($CFG->dirroot . '/calendar/lib.php');
 855  
 856          $this->resetAfterTest();
 857          $course = $this->getDataGenerator()->create_course();
 858          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 859  
 860          $this->setAdminUser();
 861  
 862          $userid = $student->id;
 863          $duedate = time();
 864          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 865  
 866          $instance = $assign->get_instance();
 867          $event = new \calendar_event((object)[
 868              'userid' => $userid
 869          ]);
 870  
 871          $this->assertFalse($assign->is_override_calendar_event($event));
 872      }
 873  
 874      /**
 875       * Return false if there is overrides for this use but they belong to another assign
 876       * instance.
 877       */
 878      public function test_assign_is_override_calendar_event_different_assign_instance() {
 879          global $CFG, $DB;
 880          require_once($CFG->dirroot . '/calendar/lib.php');
 881  
 882          $this->resetAfterTest();
 883          $course = $this->getDataGenerator()->create_course();
 884          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 885  
 886          $this->setAdminUser();
 887  
 888          $duedate = time();
 889          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 890          $instance = $assign->get_instance();
 891  
 892          $otherassign = $this->create_instance($course, ['duedate' => $duedate]);
 893          $otherinstance = $otherassign->get_instance();
 894  
 895          $event = new \calendar_event((object) [
 896              'modulename' => 'assign',
 897              'instance' => $instance->id,
 898              'userid' => $student->id,
 899          ]);
 900  
 901          $DB->insert_record('assign_overrides', (object) [
 902                  'assignid' => $otherinstance->id,
 903                  'userid' => $student->id,
 904              ]);
 905  
 906          $this->assertFalse($assign->is_override_calendar_event($event));
 907      }
 908  
 909      /**
 910       * Return true if there is a user override for this event and assign instance.
 911       */
 912      public function test_assign_is_override_calendar_event_user_override() {
 913          global $CFG, $DB;
 914          require_once($CFG->dirroot . '/calendar/lib.php');
 915  
 916          $this->resetAfterTest();
 917          $course = $this->getDataGenerator()->create_course();
 918          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 919  
 920          $this->setAdminUser();
 921  
 922          $duedate = time();
 923          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 924  
 925          $instance = $assign->get_instance();
 926          $event = new \calendar_event((object) [
 927              'modulename' => 'assign',
 928              'instance' => $instance->id,
 929              'userid' => $student->id,
 930          ]);
 931  
 932  
 933          $DB->insert_record('assign_overrides', (object) [
 934                  'assignid' => $instance->id,
 935                  'userid' => $student->id,
 936              ]);
 937  
 938          $this->assertTrue($assign->is_override_calendar_event($event));
 939      }
 940  
 941      /**
 942       * Return true if there is a group override for the event and assign instance.
 943       */
 944      public function test_assign_is_override_calendar_event_group_override() {
 945          global $CFG, $DB;
 946          require_once($CFG->dirroot . '/calendar/lib.php');
 947  
 948          $this->resetAfterTest();
 949          $course = $this->getDataGenerator()->create_course();
 950  
 951          $this->setAdminUser();
 952  
 953          $duedate = time();
 954          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 955          $instance = $assign->get_instance();
 956          $group = $this->getDataGenerator()->create_group(array('courseid' => $instance->course));
 957  
 958          $event = new \calendar_event((object) [
 959              'modulename' => 'assign',
 960              'instance' => $instance->id,
 961              'groupid' => $group->id,
 962          ]);
 963  
 964          $DB->insert_record('assign_overrides', (object) [
 965                  'assignid' => $instance->id,
 966                  'groupid' => $group->id,
 967              ]);
 968  
 969          $this->assertTrue($assign->is_override_calendar_event($event));
 970      }
 971  
 972      /**
 973       * Unknown event types should not have any limit restrictions returned.
 974       */
 975      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_unkown_event_type() {
 976          global $CFG;
 977          require_once($CFG->dirroot . '/calendar/lib.php');
 978  
 979          $this->resetAfterTest();
 980          $course = $this->getDataGenerator()->create_course();
 981  
 982          $this->setAdminUser();
 983  
 984          $duedate = time();
 985          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 986          $instance = $assign->get_instance();
 987  
 988          $event = new \calendar_event((object) [
 989              'courseid' => $instance->course,
 990              'modulename' => 'assign',
 991              'instance' => $instance->id,
 992              'eventtype' => 'SOME RANDOM EVENT'
 993          ]);
 994  
 995          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
 996          $this->assertNull($min);
 997          $this->assertNull($max);
 998      }
 999  
1000      /**
1001       * Override events should not have any limit restrictions returned.
1002       */
1003      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_override_event() {
1004          global $CFG, $DB;
1005          require_once($CFG->dirroot . '/calendar/lib.php');
1006  
1007          $this->resetAfterTest();
1008          $course = $this->getDataGenerator()->create_course();
1009          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1010  
1011          $this->setAdminUser();
1012  
1013          $duedate = time();
1014          $assign = $this->create_instance($course, ['duedate' => $duedate]);
1015          $instance = $assign->get_instance();
1016  
1017          $event = new \calendar_event((object) [
1018              'courseid' => $instance->course,
1019              'modulename' => 'assign',
1020              'instance' => $instance->id,
1021              'userid' => $student->id,
1022              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1023          ]);
1024  
1025          $record = (object) [
1026              'assignid' => $instance->id,
1027              'userid' => $student->id,
1028          ];
1029  
1030          $DB->insert_record('assign_overrides', $record);
1031  
1032          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1033          $this->assertFalse($min);
1034          $this->assertFalse($max);
1035      }
1036  
1037      /**
1038       * Assignments configured without a submissions from and cutoff date should not have
1039       * any limits applied.
1040       */
1041      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_no_limit() {
1042          global $CFG, $DB;
1043          require_once($CFG->dirroot . '/calendar/lib.php');
1044  
1045          $this->resetAfterTest();
1046          $course = $this->getDataGenerator()->create_course();
1047  
1048          $this->setAdminUser();
1049  
1050          $duedate = time();
1051          $assign = $this->create_instance($course, [
1052              'duedate' => $duedate,
1053              'allowsubmissionsfromdate' => 0,
1054              'cutoffdate' => 0,
1055          ]);
1056          $instance = $assign->get_instance();
1057  
1058          $event = new \calendar_event((object) [
1059              'courseid' => $instance->course,
1060              'modulename' => 'assign',
1061              'instance' => $instance->id,
1062              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1063          ]);
1064  
1065          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1066          $this->assertNull($min);
1067          $this->assertNull($max);
1068      }
1069  
1070      /**
1071       * Assignments should be bottom and top bound by the submissions from date and cutoff date
1072       * respectively.
1073       */
1074      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_with_limits() {
1075          global $CFG, $DB;
1076          require_once($CFG->dirroot . '/calendar/lib.php');
1077  
1078          $this->resetAfterTest();
1079          $course = $this->getDataGenerator()->create_course();
1080  
1081          $this->setAdminUser();
1082  
1083          $duedate = time();
1084          $submissionsfromdate = $duedate - DAYSECS;
1085          $cutoffdate = $duedate + DAYSECS;
1086          $assign = $this->create_instance($course, [
1087              'duedate' => $duedate,
1088              'allowsubmissionsfromdate' => $submissionsfromdate,
1089              'cutoffdate' => $cutoffdate,
1090          ]);
1091          $instance = $assign->get_instance();
1092  
1093          $event = new \calendar_event((object) [
1094              'courseid' => $instance->course,
1095              'modulename' => 'assign',
1096              'instance' => $instance->id,
1097              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1098          ]);
1099  
1100          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1101          $this->assertEquals($submissionsfromdate, $min[0]);
1102          $this->assertNotEmpty($min[1]);
1103          $this->assertEquals($cutoffdate, $max[0]);
1104          $this->assertNotEmpty($max[1]);
1105      }
1106  
1107      /**
1108       * Assignment grading due date should not have any limits of no due date and cutoff date is set.
1109       */
1110      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_no_limit() {
1111          global $CFG, $DB;
1112          require_once($CFG->dirroot . '/calendar/lib.php');
1113  
1114          $this->resetAfterTest();
1115          $course = $this->getDataGenerator()->create_course();
1116  
1117          $this->setAdminUser();
1118  
1119          $assign = $this->create_instance($course, [
1120              'duedate' => 0,
1121              'allowsubmissionsfromdate' => 0,
1122              'cutoffdate' => 0,
1123          ]);
1124          $instance = $assign->get_instance();
1125  
1126          $event = new \calendar_event((object) [
1127              'courseid' => $instance->course,
1128              'modulename' => 'assign',
1129              'instance' => $instance->id,
1130              'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
1131          ]);
1132  
1133          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1134          $this->assertNull($min);
1135          $this->assertNull($max);
1136      }
1137  
1138      /**
1139       * Assignment grading due event is minimum bound by the due date, if it is set.
1140       */
1141      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_with_due_date() {
1142          global $CFG, $DB;
1143          require_once($CFG->dirroot . '/calendar/lib.php');
1144  
1145          $this->resetAfterTest();
1146          $course = $this->getDataGenerator()->create_course();
1147  
1148          $this->setAdminUser();
1149  
1150          $duedate = time();
1151          $assign = $this->create_instance($course, ['duedate' => $duedate]);
1152          $instance = $assign->get_instance();
1153  
1154          $event = new \calendar_event((object) [
1155              'courseid' => $instance->course,
1156              'modulename' => 'assign',
1157              'instance' => $instance->id,
1158              'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
1159          ]);
1160  
1161          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1162          $this->assertEquals($duedate, $min[0]);
1163          $this->assertNotEmpty($min[1]);
1164          $this->assertNull($max);
1165      }
1166  
1167      /**
1168       * Non due date events should not update the assignment due date.
1169       */
1170      public function test_mod_assign_core_calendar_event_timestart_updated_non_due_event() {
1171          global $CFG, $DB;
1172          require_once($CFG->dirroot . '/calendar/lib.php');
1173  
1174          $this->resetAfterTest();
1175          $course = $this->getDataGenerator()->create_course();
1176          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1177  
1178          $this->setAdminUser();
1179  
1180          $duedate = time();
1181          $submissionsfromdate = $duedate - DAYSECS;
1182          $cutoffdate = $duedate + DAYSECS;
1183          $assign = $this->create_instance($course, [
1184              'duedate' => $duedate,
1185              'allowsubmissionsfromdate' => $submissionsfromdate,
1186              'cutoffdate' => $cutoffdate,
1187          ]);
1188          $instance = $assign->get_instance();
1189  
1190          $event = new \calendar_event((object) [
1191              'courseid' => $instance->course,
1192              'modulename' => 'assign',
1193              'instance' => $instance->id,
1194              'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE,
1195              'timestart' => $duedate + 1
1196          ]);
1197  
1198          mod_assign_core_calendar_event_timestart_updated($event, $instance);
1199  
1200          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1201          $this->assertEquals($duedate, $newinstance->duedate);
1202      }
1203  
1204      /**
1205       * Due date override events should not change the assignment due date.
1206       */
1207      public function test_mod_assign_core_calendar_event_timestart_updated_due_event_override() {
1208          global $CFG, $DB;
1209          require_once($CFG->dirroot . '/calendar/lib.php');
1210  
1211          $this->resetAfterTest();
1212          $course = $this->getDataGenerator()->create_course();
1213          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1214  
1215          $this->setAdminUser();
1216  
1217          $duedate = time();
1218          $submissionsfromdate = $duedate - DAYSECS;
1219          $cutoffdate = $duedate + DAYSECS;
1220          $assign = $this->create_instance($course, [
1221              'duedate' => $duedate,
1222              'allowsubmissionsfromdate' => $submissionsfromdate,
1223              'cutoffdate' => $cutoffdate,
1224          ]);
1225          $instance = $assign->get_instance();
1226  
1227          $event = new \calendar_event((object) [
1228              'courseid' => $instance->course,
1229              'modulename' => 'assign',
1230              'instance' => $instance->id,
1231              'userid' => $student->id,
1232              'eventtype' => ASSIGN_EVENT_TYPE_DUE,
1233              'timestart' => $duedate + 1
1234          ]);
1235  
1236          $record = (object) [
1237              'assignid' => $instance->id,
1238              'userid' => $student->id,
1239              'duedate' => $duedate + 1,
1240          ];
1241  
1242          $DB->insert_record('assign_overrides', $record);
1243  
1244          mod_assign_core_calendar_event_timestart_updated($event, $instance);
1245  
1246          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1247          $this->assertEquals($duedate, $newinstance->duedate);
1248      }
1249  
1250      /**
1251       * Due date events should update the assignment due date.
1252       */
1253      public function test_mod_assign_core_calendar_event_timestart_updated_due_event() {
1254          global $CFG, $DB;
1255          require_once($CFG->dirroot . '/calendar/lib.php');
1256  
1257          $this->resetAfterTest();
1258          $course = $this->getDataGenerator()->create_course();
1259          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1260  
1261          $this->setAdminUser();
1262  
1263          $duedate = time();
1264          $newduedate = $duedate + 1;
1265          $submissionsfromdate = $duedate - DAYSECS;
1266          $cutoffdate = $duedate + DAYSECS;
1267          $assign = $this->create_instance($course, [
1268              'duedate' => $duedate,
1269              'allowsubmissionsfromdate' => $submissionsfromdate,
1270              'cutoffdate' => $cutoffdate,
1271          ]);
1272          $instance = $assign->get_instance();
1273  
1274          $event = new \calendar_event((object) [
1275              'courseid' => $instance->course,
1276              'modulename' => 'assign',
1277              'instance' => $instance->id,
1278              'eventtype' => ASSIGN_EVENT_TYPE_DUE,
1279              'timestart' => $newduedate
1280          ]);
1281  
1282          mod_assign_core_calendar_event_timestart_updated($event, $instance);
1283  
1284          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1285          $this->assertEquals($newduedate, $newinstance->duedate);
1286      }
1287  
1288      /**
1289       * If a student somehow finds a way to update the due date calendar event
1290       * then the callback should not be executed to update the assignment due
1291       * date as well otherwise that would be a security issue.
1292       */
1293      public function test_student_role_cant_update_due_event() {
1294          global $CFG, $DB;
1295          require_once($CFG->dirroot . '/calendar/lib.php');
1296  
1297          $this->resetAfterTest();
1298          $course = $this->getDataGenerator()->create_course();
1299          $context = \context_course::instance($course->id);
1300  
1301          $roleid = $this->getDataGenerator()->create_role();
1302          $role = $DB->get_record('role', ['id' => $roleid]);
1303          $user = $this->getDataGenerator()->create_and_enrol($course, $role->shortname);
1304  
1305          $this->setAdminUser();
1306  
1307          $mapper = calendar_event_container::get_event_mapper();
1308          $now = time();
1309          $duedate = (new \DateTime())->setTimestamp($now);
1310          $newduedate = (new \DateTime())->setTimestamp($now)->modify('+1 day');
1311          $assign = $this->create_instance($course, [
1312              'course' => $course->id,
1313              'duedate' => $duedate->getTimestamp(),
1314          ]);
1315          $instance = $assign->get_instance();
1316  
1317          $record = $DB->get_record('event', [
1318              'courseid' => $course->id,
1319              'modulename' => 'assign',
1320              'instance' => $instance->id,
1321              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1322          ]);
1323  
1324          $event = new \calendar_event($record);
1325  
1326          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1327          assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true);
1328  
1329          $this->setUser($user);
1330  
1331          calendar_local_api::update_event_start_day(
1332              $mapper->from_legacy_event_to_event($event),
1333              $newduedate
1334          );
1335  
1336          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1337          $newevent = \calendar_event::load($event->id);
1338          // The due date shouldn't have changed even though we updated the calendar
1339          // event.
1340          $this->assertEquals($duedate->getTimestamp(), $newinstance->duedate);
1341          $this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
1342      }
1343  
1344      /**
1345       * A teacher with the capability to modify an assignment module should be
1346       * able to update the assignment due date by changing the due date calendar
1347       * event.
1348       */
1349      public function test_teacher_role_can_update_due_event() {
1350          global $CFG, $DB;
1351          require_once($CFG->dirroot . '/calendar/lib.php');
1352  
1353          $this->resetAfterTest();
1354          $course = $this->getDataGenerator()->create_course();
1355          $context = \context_course::instance($course->id);
1356          $user = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1357          $roleid = $DB->get_field('role', 'id', ['shortname' => 'teacher']);
1358  
1359          $this->setAdminUser();
1360  
1361          $mapper = calendar_event_container::get_event_mapper();
1362          $now = time();
1363          $duedate = (new \DateTime())->setTimestamp($now);
1364          $newduedate = (new \DateTime())->setTimestamp($now)->modify('+1 day');
1365          $assign = $this->create_instance($course, [
1366              'course' => $course->id,
1367              'duedate' => $duedate->getTimestamp(),
1368          ]);
1369          $instance = $assign->get_instance();
1370  
1371          $record = $DB->get_record('event', [
1372              'courseid' => $course->id,
1373              'modulename' => 'assign',
1374              'instance' => $instance->id,
1375              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1376          ]);
1377  
1378          $event = new \calendar_event($record);
1379  
1380          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1381          assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true);
1382  
1383          $this->setUser($user);
1384          // Trigger and capture the event when adding a contact.
1385          $sink = $this->redirectEvents();
1386  
1387          calendar_local_api::update_event_start_day(
1388              $mapper->from_legacy_event_to_event($event),
1389              $newduedate
1390          );
1391  
1392          $triggeredevents = $sink->get_events();
1393          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
1394              return is_a($e, 'core\event\course_module_updated');
1395          });
1396  
1397          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1398          $newevent = \calendar_event::load($event->id);
1399          // The due date shouldn't have changed even though we updated the calendar
1400          // event.
1401          $this->assertEquals($newduedate->getTimestamp(), $newinstance->duedate);
1402          $this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
1403          // Confirm that a module updated event is fired when the module
1404          // is changed.
1405          $this->assertNotEmpty($moduleupdatedevents);
1406      }
1407  
1408      /**
1409       * A user who does not have capabilities to add events to the calendar should be able to create an assignment.
1410       */
1411      public function test_creation_with_no_calendar_capabilities() {
1412          $this->resetAfterTest();
1413          $course = self::getDataGenerator()->create_course();
1414          $context = \context_course::instance($course->id);
1415          $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
1416          $roleid = self::getDataGenerator()->create_role();
1417          self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
1418          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
1419          $generator = self::getDataGenerator()->get_plugin_generator('mod_assign');
1420          // Create an instance as a user without the calendar capabilities.
1421          $this->setUser($user);
1422          $time = time();
1423          $params = array(
1424              'course' => $course->id,
1425              'allowsubmissionsfromdate' => $time,
1426              'duedate' => $time + 500,
1427              'cutoffdate' => $time + 600,
1428              'gradingduedate' => $time + 700,
1429          );
1430          $generator->create_instance($params);
1431      }
1432  }