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