Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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