Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 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   * 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      /**
 524       * Test group submissions.
 525       * @covers \assign::mod_assign_core_calendar_provide_event_action
 526       */
 527      public function test_assign_core_calendar_provide_event_action_duedate_for_group_assignment(): void {
 528          $this->resetAfterTest();
 529          $course = $this->getDataGenerator()->create_course();
 530          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 531          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 532          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 533          $group = $this->getDataGenerator()->create_group([
 534              'courseid' => $course->id,
 535          ]);
 536          groups_add_member($group->id, $student1->id);
 537          groups_add_member($group->id, $student2->id);
 538  
 539          $assign = $this->create_instance($course, [
 540              'assignsubmission_onlinetext_enabled' => 1,
 541              'requiresubmissionstatement' => 1,
 542              'teamsubmission' => 1,
 543              'completion' => 0,
 544              'completionsubmit' => 1,
 545          ]);
 546  
 547          // Create a calendar event.
 548          $this->setAdminUser();
 549          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 550          $this->setUser();
 551  
 552          $factory = new \core_calendar\action_factory();
 553          $actionevent1 = mod_assign_core_calendar_provide_event_action($event, $factory, $student1->id);
 554          $actionevent2 = mod_assign_core_calendar_provide_event_action($event, $factory, $student2->id);
 555  
 556          // Confirm the events were decorated.
 557          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent1);
 558          $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent1->get_name());
 559          $this->assertInstanceOf('moodle_url', $actionevent1->get_url());
 560          $this->assertEquals(1, $actionevent1->get_item_count());
 561          $this->assertTrue($actionevent1->is_actionable());
 562  
 563          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent2);
 564          $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent2->get_name());
 565          $this->assertInstanceOf('moodle_url', $actionevent2->get_url());
 566          $this->assertEquals(1, $actionevent2->get_item_count());
 567          $this->assertTrue($actionevent2->is_actionable());
 568  
 569          // Submit as the student.
 570          $this->add_submission($student1, $assign);
 571          $this->submit_for_grading($student1, $assign, ['submissionstatement' => 'Hello, world!']);
 572  
 573          // Create a new calendar event.
 574          $this->setAdminUser();
 575          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 576          $this->setUser();
 577  
 578          // Confirm there were no events to action.
 579          $actionevent1 = mod_assign_core_calendar_provide_event_action($event, $factory, $student1->id);
 580          $this->assertNull($actionevent1);
 581  
 582          $actionevent2 = mod_assign_core_calendar_provide_event_action($event, $factory, $student2->id);
 583          $this->assertNull($actionevent2);
 584      }
 585  
 586      public function test_assign_core_calendar_provide_event_action_gradingduedate_as_teacher() {
 587          $this->resetAfterTest();
 588          $course = $this->getDataGenerator()->create_course();
 589          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 590          $assign = $this->create_instance($course);
 591  
 592          // Create a calendar event.
 593          $this->setAdminUser();
 594          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 595  
 596          $this->setUser($teacher);
 597          $factory = new \core_calendar\action_factory();
 598          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 599  
 600          // Confirm the event was decorated.
 601          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 602          $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
 603          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 604          $this->assertEquals(0, $actionevent->get_item_count());
 605          $this->assertTrue($actionevent->is_actionable());
 606      }
 607  
 608      public function test_assign_core_calendar_provide_event_action_gradingduedate_for_teacher() {
 609          $this->resetAfterTest();
 610          $course = $this->getDataGenerator()->create_course();
 611          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 612          $assign = $this->create_instance($course);
 613  
 614          // Create a calendar event.
 615          $this->setAdminUser();
 616          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 617  
 618          // Now, log out.
 619          $this->setUser();
 620  
 621          // Decorate action event for a teacher.
 622          $factory = new \core_calendar\action_factory();
 623          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $teacher->id);
 624  
 625          // Confirm the event was decorated.
 626          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 627          $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
 628          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 629          $this->assertEquals(0, $actionevent->get_item_count());
 630          $this->assertTrue($actionevent->is_actionable());
 631      }
 632  
 633      public function test_assign_core_calendar_provide_event_action_gradingduedate_as_student() {
 634          $this->resetAfterTest();
 635          $course = $this->getDataGenerator()->create_course();
 636          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 637          $assign = $this->create_instance($course);
 638  
 639          // Create a calendar event.
 640          $this->setAdminUser();
 641          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 642  
 643          $this->setUser($student);
 644          $factory = new \core_calendar\action_factory();
 645          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 646  
 647          // Confirm the event was decorated.
 648          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 649          $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
 650          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 651          $this->assertEquals(0, $actionevent->get_item_count());
 652          $this->assertFalse($actionevent->is_actionable());
 653      }
 654  
 655      public function test_assign_core_calendar_provide_event_action_gradingduedate_for_student() {
 656          $this->resetAfterTest();
 657          $course = $this->getDataGenerator()->create_course();
 658          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 659          $assign = $this->create_instance($course);
 660  
 661          // Create a calendar event.
 662          $this->setAdminUser();
 663          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
 664  
 665          // Now, log out.
 666          $this->setUser();
 667  
 668          // Decorate action event for a student.
 669          $factory = new \core_calendar\action_factory();
 670          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
 671  
 672          // Confirm the event was decorated.
 673          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 674          $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
 675          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 676          $this->assertEquals(0, $actionevent->get_item_count());
 677          $this->assertFalse($actionevent->is_actionable());
 678      }
 679  
 680      public function test_assign_core_calendar_provide_event_action_duedate_as_student_submitted() {
 681          $this->resetAfterTest();
 682          $course = $this->getDataGenerator()->create_course();
 683          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 684          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 685          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
 686  
 687          $this->setAdminUser();
 688  
 689          // Create a calendar event.
 690          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 691  
 692          // Create an action factory.
 693          $factory = new \core_calendar\action_factory();
 694  
 695          // Submit as the student.
 696          $this->add_submission($student, $assign);
 697          $this->submit_for_grading($student, $assign);
 698  
 699          // Confirm there was no event to action.
 700          $factory = new \core_calendar\action_factory();
 701          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 702          $this->assertNull($actionevent);
 703      }
 704  
 705      public function test_assign_core_calendar_provide_event_action_duedate_for_student_submitted() {
 706          $this->resetAfterTest();
 707          $course = $this->getDataGenerator()->create_course();
 708          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 709          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 710          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
 711  
 712          $this->setAdminUser();
 713  
 714          // Create a calendar event.
 715          $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
 716  
 717          // Create an action factory.
 718          $factory = new \core_calendar\action_factory();
 719  
 720          // Submit as the student.
 721          $this->add_submission($student, $assign);
 722          $this->submit_for_grading($student, $assign);
 723  
 724          // Now, log out.
 725          $this->setUser();
 726  
 727          // Confirm there was no event to action.
 728          $factory = new \core_calendar\action_factory();
 729          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
 730          $this->assertNull($actionevent);
 731      }
 732  
 733      public function test_assign_core_calendar_provide_event_action_already_completed() {
 734          $this->resetAfterTest();
 735          set_config('enablecompletion', 1);
 736          $this->setAdminUser();
 737  
 738          // Create the activity.
 739          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 740          $assign = $this->create_instance($course,
 741           ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]);
 742  
 743          // Get some additional data.
 744          $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
 745  
 746          // Create a calendar event.
 747          $event = $this->create_action_event($course, $assign,
 748              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 749  
 750          // Mark the activity as completed.
 751          $completion = new \completion_info($course);
 752          $completion->set_module_viewed($cm);
 753  
 754          // Create an action factory.
 755          $factory = new \core_calendar\action_factory();
 756  
 757          // Decorate action event.
 758          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
 759  
 760          // Ensure result was null.
 761          $this->assertNull($actionevent);
 762      }
 763  
 764      public function test_assign_core_calendar_provide_event_action_already_completed_for_user() {
 765          $this->resetAfterTest();
 766          set_config('enablecompletion', 1);
 767          $this->setAdminUser();
 768  
 769          // Create the activity.
 770          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 771          $assign = $this->create_instance($course,
 772           ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]);
 773  
 774          // Enrol a student in the course.
 775          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 776  
 777          // Get some additional data.
 778          $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
 779  
 780          // Create a calendar event.
 781          $event = $this->create_action_event($course, $assign,
 782              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 783  
 784          // Mark the activity as completed for the student.
 785          $completion = new \completion_info($course);
 786          $completion->set_module_viewed($cm, $student->id);
 787  
 788          // Create an action factory.
 789          $factory = new \core_calendar\action_factory();
 790  
 791          // Decorate action event for the student.
 792          $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
 793  
 794          // Ensure result was null.
 795          $this->assertNull($actionevent);
 796      }
 797  
 798      /**
 799       * Creates an action event.
 800       *
 801       * @param \stdClass $course The course the assignment is in
 802       * @param assign $assign The assignment to create an event for
 803       * @param string $eventtype The event type. eg. ASSIGN_EVENT_TYPE_DUE.
 804       * @return bool|calendar_event
 805       */
 806      private function create_action_event($course, $assign, $eventtype) {
 807          $event = new \stdClass();
 808          $event->name = 'Calendar event';
 809          $event->modulename  = 'assign';
 810          $event->courseid = $course->id;
 811          $event->instance = $assign->get_instance()->id;
 812          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 813          $event->eventtype = $eventtype;
 814          $event->timestart = time();
 815  
 816          return \calendar_event::create($event);
 817      }
 818  
 819      /**
 820       * Test the callback responsible for returning the completion rule descriptions.
 821       * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
 822       * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
 823       */
 824      public function test_mod_assign_completion_get_active_rule_descriptions() {
 825          $this->resetAfterTest();
 826          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 827  
 828          $this->setAdminUser();
 829  
 830          // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
 831          $cm1 = $this->create_instance($course, ['completion' => '2', 'completionsubmit' => '1'])->get_course_module();
 832          $cm2 = $this->create_instance($course, ['completion' => '2', 'completionsubmit' => '0'])->get_course_module();
 833  
 834          // Data for the stdClass input type.
 835          // This type of input would occur when checking the default completion rules for an activity type, where we don't have
 836          // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
 837          $moddefaults = (object) [
 838              'customdata' => [
 839                  'customcompletionrules' => [
 840                      'completionsubmit' => '1',
 841                  ],
 842              ],
 843              'completion' => 2,
 844          ];
 845  
 846          $activeruledescriptions = [get_string('completionsubmit', 'assign')];
 847          $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
 848          $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($cm2), []);
 849          $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
 850          $this->assertEquals(mod_assign_get_completion_active_rule_descriptions(new \stdClass()), []);
 851      }
 852  
 853      /**
 854       * Test that if some grades are not set, they are left alone and not rescaled
 855       */
 856      public function test_assign_rescale_activity_grades_some_unset() {
 857          $this->resetAfterTest();
 858          $course = $this->getDataGenerator()->create_course();
 859          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 860          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 861          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
 862  
 863          // As a teacher.
 864          $this->setUser($teacher);
 865          $assign = $this->create_instance($course);
 866  
 867          // Grade the student.
 868          $data = ['grade' => 50];
 869          $assign->testable_apply_grade_to_user((object)$data, $student->id, 0);
 870  
 871          // Try getting another students grade. This will give a grade of ASSIGN_GRADE_NOT_SET (-1).
 872          $assign->get_user_grade($otherstudent->id, true);
 873  
 874          // Rescale.
 875          assign_rescale_activity_grades($course, $assign->get_course_module(), 0, 100, 0, 10);
 876  
 877          // Get the grades for both students.
 878          $studentgrade = $assign->get_user_grade($student->id, true);
 879          $otherstudentgrade = $assign->get_user_grade($otherstudent->id, true);
 880  
 881          // Make sure the real grade is scaled, but the ASSIGN_GRADE_NOT_SET stays the same.
 882          $this->assertEquals($studentgrade->grade, 5);
 883          $this->assertEquals($otherstudentgrade->grade, ASSIGN_GRADE_NOT_SET);
 884      }
 885  
 886      /**
 887       * Return false when there are not overrides for this assign instance.
 888       */
 889      public function test_assign_is_override_calendar_event_no_override() {
 890          global $CFG, $DB;
 891          require_once($CFG->dirroot . '/calendar/lib.php');
 892  
 893          $this->resetAfterTest();
 894          $course = $this->getDataGenerator()->create_course();
 895          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 896  
 897          $this->setAdminUser();
 898  
 899          $duedate = time();
 900          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 901  
 902          $instance = $assign->get_instance();
 903          $event = new \calendar_event((object)[
 904              'modulename' => 'assign',
 905              'instance' => $instance->id,
 906              'userid' => $student->id,
 907          ]);
 908  
 909          $this->assertFalse($assign->is_override_calendar_event($event));
 910      }
 911  
 912      /**
 913       * Return false if the given event isn't an assign module event.
 914       */
 915      public function test_assign_is_override_calendar_event_no_nodule_event() {
 916          global $CFG, $DB;
 917          require_once($CFG->dirroot . '/calendar/lib.php');
 918  
 919          $this->resetAfterTest();
 920          $course = $this->getDataGenerator()->create_course();
 921          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 922  
 923          $this->setAdminUser();
 924  
 925          $userid = $student->id;
 926          $duedate = time();
 927          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 928  
 929          $instance = $assign->get_instance();
 930          $event = new \calendar_event((object)[
 931              'userid' => $userid
 932          ]);
 933  
 934          $this->assertFalse($assign->is_override_calendar_event($event));
 935      }
 936  
 937      /**
 938       * Return false if there is overrides for this use but they belong to another assign
 939       * instance.
 940       */
 941      public function test_assign_is_override_calendar_event_different_assign_instance() {
 942          global $CFG, $DB;
 943          require_once($CFG->dirroot . '/calendar/lib.php');
 944  
 945          $this->resetAfterTest();
 946          $course = $this->getDataGenerator()->create_course();
 947          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 948  
 949          $this->setAdminUser();
 950  
 951          $duedate = time();
 952          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 953          $instance = $assign->get_instance();
 954  
 955          $otherassign = $this->create_instance($course, ['duedate' => $duedate]);
 956          $otherinstance = $otherassign->get_instance();
 957  
 958          $event = new \calendar_event((object) [
 959              'modulename' => 'assign',
 960              'instance' => $instance->id,
 961              'userid' => $student->id,
 962          ]);
 963  
 964          $DB->insert_record('assign_overrides', (object) [
 965                  'assignid' => $otherinstance->id,
 966                  'userid' => $student->id,
 967              ]);
 968  
 969          $this->assertFalse($assign->is_override_calendar_event($event));
 970      }
 971  
 972      /**
 973       * Return true if there is a user override for this event and assign instance.
 974       */
 975      public function test_assign_is_override_calendar_event_user_override() {
 976          global $CFG, $DB;
 977          require_once($CFG->dirroot . '/calendar/lib.php');
 978  
 979          $this->resetAfterTest();
 980          $course = $this->getDataGenerator()->create_course();
 981          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 982  
 983          $this->setAdminUser();
 984  
 985          $duedate = time();
 986          $assign = $this->create_instance($course, ['duedate' => $duedate]);
 987  
 988          $instance = $assign->get_instance();
 989          $event = new \calendar_event((object) [
 990              'modulename' => 'assign',
 991              'instance' => $instance->id,
 992              'userid' => $student->id,
 993          ]);
 994  
 995  
 996          $DB->insert_record('assign_overrides', (object) [
 997                  'assignid' => $instance->id,
 998                  'userid' => $student->id,
 999              ]);
1000  
1001          $this->assertTrue($assign->is_override_calendar_event($event));
1002      }
1003  
1004      /**
1005       * Return true if there is a group override for the event and assign instance.
1006       */
1007      public function test_assign_is_override_calendar_event_group_override() {
1008          global $CFG, $DB;
1009          require_once($CFG->dirroot . '/calendar/lib.php');
1010  
1011          $this->resetAfterTest();
1012          $course = $this->getDataGenerator()->create_course();
1013  
1014          $this->setAdminUser();
1015  
1016          $duedate = time();
1017          $assign = $this->create_instance($course, ['duedate' => $duedate]);
1018          $instance = $assign->get_instance();
1019          $group = $this->getDataGenerator()->create_group(array('courseid' => $instance->course));
1020  
1021          $event = new \calendar_event((object) [
1022              'modulename' => 'assign',
1023              'instance' => $instance->id,
1024              'groupid' => $group->id,
1025          ]);
1026  
1027          $DB->insert_record('assign_overrides', (object) [
1028                  'assignid' => $instance->id,
1029                  'groupid' => $group->id,
1030              ]);
1031  
1032          $this->assertTrue($assign->is_override_calendar_event($event));
1033      }
1034  
1035      /**
1036       * Unknown event types should not have any limit restrictions returned.
1037       */
1038      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_unkown_event_type() {
1039          global $CFG;
1040          require_once($CFG->dirroot . '/calendar/lib.php');
1041  
1042          $this->resetAfterTest();
1043          $course = $this->getDataGenerator()->create_course();
1044  
1045          $this->setAdminUser();
1046  
1047          $duedate = time();
1048          $assign = $this->create_instance($course, ['duedate' => $duedate]);
1049          $instance = $assign->get_instance();
1050  
1051          $event = new \calendar_event((object) [
1052              'courseid' => $instance->course,
1053              'modulename' => 'assign',
1054              'instance' => $instance->id,
1055              'eventtype' => 'SOME RANDOM EVENT'
1056          ]);
1057  
1058          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1059          $this->assertNull($min);
1060          $this->assertNull($max);
1061      }
1062  
1063      /**
1064       * Override events should not have any limit restrictions returned.
1065       */
1066      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_override_event() {
1067          global $CFG, $DB;
1068          require_once($CFG->dirroot . '/calendar/lib.php');
1069  
1070          $this->resetAfterTest();
1071          $course = $this->getDataGenerator()->create_course();
1072          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1073  
1074          $this->setAdminUser();
1075  
1076          $duedate = time();
1077          $assign = $this->create_instance($course, ['duedate' => $duedate]);
1078          $instance = $assign->get_instance();
1079  
1080          $event = new \calendar_event((object) [
1081              'courseid' => $instance->course,
1082              'modulename' => 'assign',
1083              'instance' => $instance->id,
1084              'userid' => $student->id,
1085              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1086          ]);
1087  
1088          $record = (object) [
1089              'assignid' => $instance->id,
1090              'userid' => $student->id,
1091          ];
1092  
1093          $DB->insert_record('assign_overrides', $record);
1094  
1095          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1096          $this->assertFalse($min);
1097          $this->assertFalse($max);
1098      }
1099  
1100      /**
1101       * Assignments configured without a submissions from and cutoff date should not have
1102       * any limits applied.
1103       */
1104      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_no_limit() {
1105          global $CFG, $DB;
1106          require_once($CFG->dirroot . '/calendar/lib.php');
1107  
1108          $this->resetAfterTest();
1109          $course = $this->getDataGenerator()->create_course();
1110  
1111          $this->setAdminUser();
1112  
1113          $duedate = time();
1114          $assign = $this->create_instance($course, [
1115              'duedate' => $duedate,
1116              'allowsubmissionsfromdate' => 0,
1117              'cutoffdate' => 0,
1118          ]);
1119          $instance = $assign->get_instance();
1120  
1121          $event = new \calendar_event((object) [
1122              'courseid' => $instance->course,
1123              'modulename' => 'assign',
1124              'instance' => $instance->id,
1125              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1126          ]);
1127  
1128          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1129          $this->assertNull($min);
1130          $this->assertNull($max);
1131      }
1132  
1133      /**
1134       * Assignments should be bottom and top bound by the submissions from date and cutoff date
1135       * respectively.
1136       */
1137      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_with_limits() {
1138          global $CFG, $DB;
1139          require_once($CFG->dirroot . '/calendar/lib.php');
1140  
1141          $this->resetAfterTest();
1142          $course = $this->getDataGenerator()->create_course();
1143  
1144          $this->setAdminUser();
1145  
1146          $duedate = time();
1147          $submissionsfromdate = $duedate - DAYSECS;
1148          $cutoffdate = $duedate + DAYSECS;
1149          $assign = $this->create_instance($course, [
1150              'duedate' => $duedate,
1151              'allowsubmissionsfromdate' => $submissionsfromdate,
1152              'cutoffdate' => $cutoffdate,
1153          ]);
1154          $instance = $assign->get_instance();
1155  
1156          $event = new \calendar_event((object) [
1157              'courseid' => $instance->course,
1158              'modulename' => 'assign',
1159              'instance' => $instance->id,
1160              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1161          ]);
1162  
1163          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1164          $this->assertEquals($submissionsfromdate, $min[0]);
1165          $this->assertNotEmpty($min[1]);
1166          $this->assertEquals($cutoffdate, $max[0]);
1167          $this->assertNotEmpty($max[1]);
1168      }
1169  
1170      /**
1171       * Assignment grading due date should not have any limits of no due date and cutoff date is set.
1172       */
1173      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_no_limit() {
1174          global $CFG, $DB;
1175          require_once($CFG->dirroot . '/calendar/lib.php');
1176  
1177          $this->resetAfterTest();
1178          $course = $this->getDataGenerator()->create_course();
1179  
1180          $this->setAdminUser();
1181  
1182          $assign = $this->create_instance($course, [
1183              'duedate' => 0,
1184              'allowsubmissionsfromdate' => 0,
1185              'cutoffdate' => 0,
1186          ]);
1187          $instance = $assign->get_instance();
1188  
1189          $event = new \calendar_event((object) [
1190              'courseid' => $instance->course,
1191              'modulename' => 'assign',
1192              'instance' => $instance->id,
1193              'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
1194          ]);
1195  
1196          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1197          $this->assertNull($min);
1198          $this->assertNull($max);
1199      }
1200  
1201      /**
1202       * Assignment grading due event is minimum bound by the due date, if it is set.
1203       */
1204      public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_with_due_date() {
1205          global $CFG, $DB;
1206          require_once($CFG->dirroot . '/calendar/lib.php');
1207  
1208          $this->resetAfterTest();
1209          $course = $this->getDataGenerator()->create_course();
1210  
1211          $this->setAdminUser();
1212  
1213          $duedate = time();
1214          $assign = $this->create_instance($course, ['duedate' => $duedate]);
1215          $instance = $assign->get_instance();
1216  
1217          $event = new \calendar_event((object) [
1218              'courseid' => $instance->course,
1219              'modulename' => 'assign',
1220              'instance' => $instance->id,
1221              'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
1222          ]);
1223  
1224          list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1225          $this->assertEquals($duedate, $min[0]);
1226          $this->assertNotEmpty($min[1]);
1227          $this->assertNull($max);
1228      }
1229  
1230      /**
1231       * Non due date events should not update the assignment due date.
1232       */
1233      public function test_mod_assign_core_calendar_event_timestart_updated_non_due_event() {
1234          global $CFG, $DB;
1235          require_once($CFG->dirroot . '/calendar/lib.php');
1236  
1237          $this->resetAfterTest();
1238          $course = $this->getDataGenerator()->create_course();
1239          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1240  
1241          $this->setAdminUser();
1242  
1243          $duedate = time();
1244          $submissionsfromdate = $duedate - DAYSECS;
1245          $cutoffdate = $duedate + DAYSECS;
1246          $assign = $this->create_instance($course, [
1247              'duedate' => $duedate,
1248              'allowsubmissionsfromdate' => $submissionsfromdate,
1249              'cutoffdate' => $cutoffdate,
1250          ]);
1251          $instance = $assign->get_instance();
1252  
1253          $event = new \calendar_event((object) [
1254              'courseid' => $instance->course,
1255              'modulename' => 'assign',
1256              'instance' => $instance->id,
1257              'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE,
1258              'timestart' => $duedate + 1
1259          ]);
1260  
1261          mod_assign_core_calendar_event_timestart_updated($event, $instance);
1262  
1263          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1264          $this->assertEquals($duedate, $newinstance->duedate);
1265      }
1266  
1267      /**
1268       * Due date override events should not change the assignment due date.
1269       */
1270      public function test_mod_assign_core_calendar_event_timestart_updated_due_event_override() {
1271          global $CFG, $DB;
1272          require_once($CFG->dirroot . '/calendar/lib.php');
1273  
1274          $this->resetAfterTest();
1275          $course = $this->getDataGenerator()->create_course();
1276          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1277  
1278          $this->setAdminUser();
1279  
1280          $duedate = time();
1281          $submissionsfromdate = $duedate - DAYSECS;
1282          $cutoffdate = $duedate + DAYSECS;
1283          $assign = $this->create_instance($course, [
1284              'duedate' => $duedate,
1285              'allowsubmissionsfromdate' => $submissionsfromdate,
1286              'cutoffdate' => $cutoffdate,
1287          ]);
1288          $instance = $assign->get_instance();
1289  
1290          $event = new \calendar_event((object) [
1291              'courseid' => $instance->course,
1292              'modulename' => 'assign',
1293              'instance' => $instance->id,
1294              'userid' => $student->id,
1295              'eventtype' => ASSIGN_EVENT_TYPE_DUE,
1296              'timestart' => $duedate + 1
1297          ]);
1298  
1299          $record = (object) [
1300              'assignid' => $instance->id,
1301              'userid' => $student->id,
1302              'duedate' => $duedate + 1,
1303          ];
1304  
1305          $DB->insert_record('assign_overrides', $record);
1306  
1307          mod_assign_core_calendar_event_timestart_updated($event, $instance);
1308  
1309          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1310          $this->assertEquals($duedate, $newinstance->duedate);
1311      }
1312  
1313      /**
1314       * Due date events should update the assignment due date.
1315       */
1316      public function test_mod_assign_core_calendar_event_timestart_updated_due_event() {
1317          global $CFG, $DB;
1318          require_once($CFG->dirroot . '/calendar/lib.php');
1319  
1320          $this->resetAfterTest();
1321          $course = $this->getDataGenerator()->create_course();
1322          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1323  
1324          $this->setAdminUser();
1325  
1326          $duedate = time();
1327          $newduedate = $duedate + 1;
1328          $submissionsfromdate = $duedate - DAYSECS;
1329          $cutoffdate = $duedate + DAYSECS;
1330          $assign = $this->create_instance($course, [
1331              'duedate' => $duedate,
1332              'allowsubmissionsfromdate' => $submissionsfromdate,
1333              'cutoffdate' => $cutoffdate,
1334          ]);
1335          $instance = $assign->get_instance();
1336  
1337          $event = new \calendar_event((object) [
1338              'courseid' => $instance->course,
1339              'modulename' => 'assign',
1340              'instance' => $instance->id,
1341              'eventtype' => ASSIGN_EVENT_TYPE_DUE,
1342              'timestart' => $newduedate
1343          ]);
1344  
1345          mod_assign_core_calendar_event_timestart_updated($event, $instance);
1346  
1347          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1348          $this->assertEquals($newduedate, $newinstance->duedate);
1349      }
1350  
1351      /**
1352       * If a student somehow finds a way to update the due date calendar event
1353       * then the callback should not be executed to update the assignment due
1354       * date as well otherwise that would be a security issue.
1355       */
1356      public function test_student_role_cant_update_due_event() {
1357          global $CFG, $DB;
1358          require_once($CFG->dirroot . '/calendar/lib.php');
1359  
1360          $this->resetAfterTest();
1361          $course = $this->getDataGenerator()->create_course();
1362          $context = \context_course::instance($course->id);
1363  
1364          $roleid = $this->getDataGenerator()->create_role();
1365          $role = $DB->get_record('role', ['id' => $roleid]);
1366          $user = $this->getDataGenerator()->create_and_enrol($course, $role->shortname);
1367  
1368          $this->setAdminUser();
1369  
1370          $mapper = calendar_event_container::get_event_mapper();
1371          $now = time();
1372          $duedate = (new \DateTime())->setTimestamp($now);
1373          $newduedate = (new \DateTime())->setTimestamp($now)->modify('+1 day');
1374          $assign = $this->create_instance($course, [
1375              'course' => $course->id,
1376              'duedate' => $duedate->getTimestamp(),
1377          ]);
1378          $instance = $assign->get_instance();
1379  
1380          $record = $DB->get_record('event', [
1381              'courseid' => $course->id,
1382              'modulename' => 'assign',
1383              'instance' => $instance->id,
1384              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1385          ]);
1386  
1387          $event = new \calendar_event($record);
1388  
1389          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1390          assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true);
1391  
1392          $this->setUser($user);
1393  
1394          calendar_local_api::update_event_start_day(
1395              $mapper->from_legacy_event_to_event($event),
1396              $newduedate
1397          );
1398  
1399          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1400          $newevent = \calendar_event::load($event->id);
1401          // The due date shouldn't have changed even though we updated the calendar
1402          // event.
1403          $this->assertEquals($duedate->getTimestamp(), $newinstance->duedate);
1404          $this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
1405      }
1406  
1407      /**
1408       * A teacher with the capability to modify an assignment module should be
1409       * able to update the assignment due date by changing the due date calendar
1410       * event.
1411       */
1412      public function test_teacher_role_can_update_due_event() {
1413          global $CFG, $DB;
1414          require_once($CFG->dirroot . '/calendar/lib.php');
1415  
1416          $this->resetAfterTest();
1417          $course = $this->getDataGenerator()->create_course();
1418          $context = \context_course::instance($course->id);
1419          $user = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1420          $roleid = $DB->get_field('role', 'id', ['shortname' => 'teacher']);
1421  
1422          $this->setAdminUser();
1423  
1424          $mapper = calendar_event_container::get_event_mapper();
1425          $now = time();
1426          $duedate = (new \DateTime())->setTimestamp($now);
1427          $newduedate = (new \DateTime())->setTimestamp($now)->modify('+1 day');
1428          $assign = $this->create_instance($course, [
1429              'course' => $course->id,
1430              'duedate' => $duedate->getTimestamp(),
1431          ]);
1432          $instance = $assign->get_instance();
1433  
1434          $record = $DB->get_record('event', [
1435              'courseid' => $course->id,
1436              'modulename' => 'assign',
1437              'instance' => $instance->id,
1438              'eventtype' => ASSIGN_EVENT_TYPE_DUE
1439          ]);
1440  
1441          $event = new \calendar_event($record);
1442  
1443          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1444          assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true);
1445  
1446          $this->setUser($user);
1447          // Trigger and capture the event when adding a contact.
1448          $sink = $this->redirectEvents();
1449  
1450          calendar_local_api::update_event_start_day(
1451              $mapper->from_legacy_event_to_event($event),
1452              $newduedate
1453          );
1454  
1455          $triggeredevents = $sink->get_events();
1456          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
1457              return is_a($e, 'core\event\course_module_updated');
1458          });
1459  
1460          $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1461          $newevent = \calendar_event::load($event->id);
1462          // The due date shouldn't have changed even though we updated the calendar
1463          // event.
1464          $this->assertEquals($newduedate->getTimestamp(), $newinstance->duedate);
1465          $this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
1466          // Confirm that a module updated event is fired when the module
1467          // is changed.
1468          $this->assertNotEmpty($moduleupdatedevents);
1469      }
1470  
1471      /**
1472       * A user who does not have capabilities to add events to the calendar should be able to create an assignment.
1473       */
1474      public function test_creation_with_no_calendar_capabilities() {
1475          $this->resetAfterTest();
1476          $course = self::getDataGenerator()->create_course();
1477          $context = \context_course::instance($course->id);
1478          $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
1479          $roleid = self::getDataGenerator()->create_role();
1480          self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
1481          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
1482          $generator = self::getDataGenerator()->get_plugin_generator('mod_assign');
1483          // Create an instance as a user without the calendar capabilities.
1484          $this->setUser($user);
1485          $time = time();
1486          $params = array(
1487              'course' => $course->id,
1488              'allowsubmissionsfromdate' => $time,
1489              'duedate' => $time + 500,
1490              'cutoffdate' => $time + 600,
1491              'gradingduedate' => $time + 700,
1492          );
1493          $generator->create_instance($params);
1494      }
1495  }