MDL-58768 mod_assign: Added userid param to calendar callbacks
[moodle.git] / mod / assign / tests / lib_test.php
CommitLineData
cb5b4860
DW
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
26
27defined('MOODLE_INTERNAL') || die();
28
29global $CFG;
30require_once($CFG->dirroot . '/mod/assign/lib.php');
31require_once($CFG->dirroot . '/mod/assign/locallib.php');
757d5b7c 32require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
cb5b4860 33
f4c21561
RW
34use \core_calendar\local\api as calendar_local_api;
35use \core_calendar\local\event\container as calendar_event_container;
36
cb5b4860
DW
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 */
757d5b7c
AN
43
44class mod_assign_lib_testcase extends advanced_testcase {
45
46 // Use the generator helper.
47 use mod_assign_test_generator;
6c489d2c
DW
48
49 public function test_assign_print_overview() {
50 global $DB;
81251186 51
757d5b7c
AN
52 $this->resetAfterTest();
53
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
81251186 58 $this->setAdminUser();
757d5b7c
AN
59
60 // Assignment with default values.
61 $firstassign = $this->create_instance($course, ['name' => 'First Assignment']);
62
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);
75
81251186 76 // Past assignments should not show up.
757d5b7c
AN
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 ]);
84
81251186 85 // Open assignments should show up only if relevant.
757d5b7c
AN
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);
6c489d2c 95
5533e011 96 // Check the overview as the different users.
81251186 97 // For students , open assignments should show only when there are no valid submissions.
757d5b7c 98 $this->setUser($student);
cb5b4860 99 $overview = array();
757d5b7c 100 $courses = $DB->get_records('course', array('id' => $course->id));
0e4954ec 101 assign_print_overview($courses, $overview);
e9dfeec9 102 $this->assertDebuggingCalledCount(3);
81251186 103 $this->assertEquals(1, count($overview));
757d5b7c
AN
104 $this->assertRegExp('/.*Open Assignment.*/', $overview[$course->id]['assign']); // No valid submission.
105 $this->assertNotRegExp('/.*First Assignment.*/', $overview[$course->id]['assign']); // Has valid submission.
81251186
AA
106
107 // And now submit the submission.
108 $opensubmission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
757d5b7c 109 $openassign->testable_update_submission($opensubmission, $student->id, true, false);
81251186
AA
110
111 $overview = array();
112 assign_print_overview($courses, $overview);
e9dfeec9 113 $this->assertDebuggingCalledCount(3);
81251186 114 $this->assertEquals(0, count($overview));
cb5b4860 115
757d5b7c 116 $this->setUser($teacher);
cb5b4860 117 $overview = array();
0e4954ec 118 assign_print_overview($courses, $overview);
e9dfeec9 119 $this->assertDebuggingCalledCount(3);
81251186
AA
120 $this->assertEquals(1, count($overview));
121 // Submissions without a grade.
757d5b7c 122 $this->assertRegExp('/.*Open Assignment.*/', $overview[$course->id]['assign']);
de06b633 123 $this->assertNotRegExp('/.*Assignment with submissions.*/', $overview[$course->id]['assign']);
cb5b4860 124
757d5b7c 125 $this->setUser($teacher);
cb5b4860 126 $overview = array();
0e4954ec 127 assign_print_overview($courses, $overview);
e9dfeec9 128 $this->assertDebuggingCalledCount(3);
cb5b4860 129 $this->assertEquals(1, count($overview));
81251186 130 // Submissions without a grade.
757d5b7c 131 $this->assertRegExp('/.*Open Assignment.*/', $overview[$course->id]['assign']);
de06b633 132 $this->assertNotRegExp('/.*Assignment with submissions.*/', $overview[$course->id]['assign']);
81251186
AA
133
134 // Let us grade a submission.
757d5b7c 135 $this->setUser($teacher);
81251186
AA
136 $data = new stdClass();
137 $data->grade = '50.0';
757d5b7c 138 $openassign->testable_apply_grade_to_user($data, $student->id, 0);
559cfa03
MC
139
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,
757d5b7c 142 'userid' => $student->id, 'attemptnumber' => 0));
559cfa03
MC
143 $graderecord->timemodified += 1;
144 $DB->update_record('assign_grades', $graderecord);
145
81251186
AA
146 $overview = array();
147 assign_print_overview($courses, $overview);
81251186 148 // Now assignment 4 should not show up.
de06b633
AN
149 $this->assertDebuggingCalledCount(3);
150 $this->assertEmpty($overview);
81251186 151
757d5b7c 152 $this->setUser($teacher);
81251186
AA
153 $overview = array();
154 assign_print_overview($courses, $overview);
e9dfeec9 155 $this->assertDebuggingCalledCount(3);
81251186 156 // Now assignment 4 should not show up.
de06b633 157 $this->assertEmpty($overview);
757d5b7c 158 }
81251186 159
757d5b7c
AN
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');
167
168 $this->setAdminUser();
169 $openassign = $this->create_instance($course, [
170 'duedate' => time() + DAYSECS,
171 'cutoffdate' => time() + (DAYSECS * 2),
172 ]);
173
174 $this->setUser($student);
175 $overview = [];
176 assign_print_overview([$course], $overview);
177
178 $this->assertDebuggingCalledCount(1);
81251186 179 $this->assertEquals(0, count($overview));
cb5b4860
DW
180 }
181
757d5b7c
AN
182 /**
183 * Test that assign_print_recent_activity shows ungraded submitted assignments.
184 */
cb5b4860 185 public function test_print_recent_activity() {
757d5b7c
AN
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);
cb5b4860 192
757d5b7c 193 $this->setUser($teacher);
cb5b4860 194 $this->expectOutputRegex('/submitted:/');
757d5b7c 195 assign_print_recent_activity($course, true, time() - 3600);
cb5b4860
DW
196 }
197
757d5b7c
AN
198 /**
199 * Test that assign_print_recent_activity does not display any warnings when a custom fullname has been configured.
200 */
5c7ec9f6 201 public function test_print_recent_activity_fullname() {
757d5b7c
AN
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);
5c7ec9f6 208
757d5b7c 209 $this->setUser($teacher);
5c7ec9f6
AA
210 $this->expectOutputRegex('/submitted:/');
211 set_config('fullnamedisplay', 'firstname, lastnamephonetic');
757d5b7c 212 assign_print_recent_activity($course, false, time() - 3600);
5c7ec9f6
AA
213 }
214
757d5b7c
AN
215 /**
216 * Test that assign_print_recent_activity shows the blind marking ID.
217 */
d89dc6da 218 public function test_print_recent_activity_fullname_blind_marking() {
757d5b7c
AN
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');
223
224 $assign = $this->create_instance($course, [
225 'blindmarking' => 1,
226 ]);
227 $this->add_submission($student, $assign);
228 $this->submit_for_grading($student, $assign);
229
230 $this->setUser($teacher);
231 $uniqueid = $assign->get_uniqueid_for_user($student->id);
d89dc6da
TL
232 $expectedstr = preg_quote(get_string('participant', 'mod_assign'), '/') . '.*' . $uniqueid;
233 $this->expectOutputRegex("/{$expectedstr}/");
757d5b7c 234 assign_print_recent_activity($course, false, time() - 3600);
d89dc6da
TL
235 }
236
757d5b7c
AN
237 /**
238 * Test that assign_get_recent_mod_activity fetches the assignment correctly.
239 */
cb5b4860 240 public function test_assign_get_recent_mod_activity() {
757d5b7c
AN
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);
248
249 $index = 1;
250 $activities = [
251 $index => (object) [
252 'type' => 'assign',
253 'cmid' => $assign->get_course_module()->id,
254 ],
255 ];
821ce4d6 256
757d5b7c
AN
257 $this->setUser($teacher);
258 assign_get_recent_mod_activity($activities, $index, time() - HOURSECS, $course->id, $assign->get_course_module()->id);
cb5b4860 259
757d5b7c
AN
260 $activity = $activities[1];
261 $this->assertEquals("assign", $activity->type);
262 $this->assertEquals($student->id, $activity->user->id);
cb5b4860
DW
263 }
264
757d5b7c
AN
265 /**
266 * Ensure that assign_user_complete displays information about drafts.
267 */
cb5b4860 268 public function test_assign_user_complete() {
9e3eee67 269 global $PAGE, $DB;
9c986ee0 270
757d5b7c
AN
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);
277
5533e011 278 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
cb5b4860 279
757d5b7c 280 $submission = $assign->get_user_submission($student->id, true);
9e3eee67
DW
281 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
282 $DB->update_record('assign_submission', $submission);
cb5b4860 283
cb5b4860 284 $this->expectOutputRegex('/Draft/');
757d5b7c 285 assign_user_complete($course, $student, $assign->get_course_module(), $assign->get_instance());
cb5b4860
DW
286 }
287
757d5b7c
AN
288 /**
289 * Ensure that assign_user_outline fetches updated grades.
290 */
cb5b4860 291 public function test_assign_user_outline() {
757d5b7c
AN
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);
cb5b4860 297
757d5b7c
AN
298 $this->add_submission($student, $assign);
299 $this->submit_for_grading($student, $assign);
300 $this->mark_submission($teacher, $assign, $student, 50.0);
301
302 $this->setUser($teacher);
303 $data = $assign->get_user_grade($student->id, true);
d021941d 304 $data->grade = '50.5';
cb5b4860
DW
305 $assign->update_grade($data);
306
757d5b7c 307 $result = assign_user_outline($course, $student, $assign->get_course_module(), $assign->get_instance());
cb5b4860 308
d021941d 309 $this->assertRegExp('/50.5/', $result->info);
cb5b4860
DW
310 }
311
757d5b7c
AN
312 /**
313 * Ensure that assign_get_completion_state reflects the correct status at each point.
314 */
cb5b4860
DW
315 public function test_assign_get_completion_state() {
316 global $DB;
cb5b4860 317
757d5b7c
AN
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 ]);
326
327 $this->setUser($student);
328 $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
cb5b4860 329 $this->assertFalse($result);
cb5b4860 330
757d5b7c
AN
331 $this->add_submission($student, $assign);
332 $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
333 $this->assertFalse($result);
cb5b4860 334
757d5b7c
AN
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);
338
339 $this->mark_submission($teacher, $assign, $student, 50.0);
340 $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
cb5b4860
DW
341 $this->assertTrue($result);
342 }
343
418a0d04
JP
344 /**
345 * Tests for mod_assign_refresh_events.
346 */
347 public function test_assign_refresh_events() {
348 global $DB;
757d5b7c
AN
349
350 $this->resetAfterTest();
351
418a0d04 352 $duedate = time();
bb3fc8d6 353 $newduedate = $duedate + DAYSECS;
757d5b7c 354
418a0d04
JP
355 $this->setAdminUser();
356
757d5b7c
AN
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 ]);
418a0d04 363
bb3fc8d6
JP
364 $instance = $assign->get_instance();
365 $eventparams = ['modulename' => 'assign', 'instance' => $instance->id];
757d5b7c
AN
366
367 // Make sure the calendar event for assignment 1 matches the initial due date.
bb3fc8d6
JP
368 $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
369 $this->assertEquals($eventtime, $duedate);
370
371 // Manually update assignment 1's due date.
757d5b7c 372 $DB->update_record('assign', (object) ['id' => $instance->id, 'duedate' => $newduedate]);
bb3fc8d6
JP
373
374 // Then refresh the assignment events of assignment 1's course.
757d5b7c 375 $this->assertTrue(assign_refresh_events($course->id));
418a0d04 376
bb3fc8d6
JP
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);
418a0d04 380
bb3fc8d6 381 // Create a second course and assignment.
757d5b7c
AN
382 $othercourse = $this->getDataGenerator()->create_course();;
383 $otherassign = $this->create_instance($othercourse, ['duedate' => $duedate, 'course' => $othercourse->id]);
384 $otherinstance = $otherassign->get_instance();
418a0d04 385
bb3fc8d6
JP
386 // Manually update assignment 1 and 2's due dates.
387 $newduedate += DAYSECS;
388 $DB->update_record('assign', (object)['id' => $instance->id, 'duedate' => $newduedate]);
757d5b7c 389 $DB->update_record('assign', (object)['id' => $otherinstance->id, 'duedate' => $newduedate]);
bb3fc8d6
JP
390
391 // Refresh events of all courses.
418a0d04
JP
392 $this->assertTrue(assign_refresh_events());
393
bb3fc8d6
JP
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);
397
398 // Check the due date calendar event for assignment 2.
757d5b7c 399 $eventparams['instance'] = $otherinstance->id;
bb3fc8d6
JP
400 $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
401 $this->assertEquals($eventtime, $newduedate);
402
403 // In case the course ID is passed as a numeric string.
757d5b7c 404 $this->assertTrue(assign_refresh_events('' . $course->id));
418a0d04
JP
405
406 // Non-existing course ID.
407 $this->assertFalse(assign_refresh_events(-1));
408
409 // Invalid course ID.
410 $this->assertFalse(assign_refresh_events('aaa'));
411 }
412
294dce67 413 public function test_assign_core_calendar_is_event_visible_duedate_event_as_teacher() {
757d5b7c
AN
414 $this->resetAfterTest();
415 $course = $this->getDataGenerator()->create_course();
416 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
417 $assign = $this->create_instance($course);
294dce67 418
757d5b7c 419 $this->setAdminUser();
294dce67
MN
420
421 // Create a calendar event.
757d5b7c 422 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
294dce67 423
4c114500 424 // The teacher should see the due date event.
757d5b7c 425 $this->setUser($teacher);
4c114500 426 $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
294dce67
MN
427 }
428
fda4374a
SR
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);
434
435 $this->setAdminUser();
436
437 // Create a calendar event.
438 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
439
440 // Now, log out.
441 $this->setUser();
442
443 // The teacher should see the due date event.
444 $this->assertTrue(mod_assign_core_calendar_is_event_visible($event, $teacher->id));
445 }
446
294dce67 447 public function test_assign_core_calendar_is_event_visible_duedate_event_as_student() {
757d5b7c
AN
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]);
294dce67 453
757d5b7c 454 $this->setAdminUser();
294dce67
MN
455
456 // Create a calendar event.
757d5b7c 457 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
294dce67
MN
458
459 // The student should care about the due date event.
757d5b7c 460 $this->setUser($student);
294dce67
MN
461 $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
462 }
463
fda4374a
SR
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]);
470
471 $this->setAdminUser();
472
473 // Create a calendar event.
474 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
475
476 // Now, log out.
477 $this->setUser();
478
479 // The student should care about the due date event.
480 $this->assertTrue(mod_assign_core_calendar_is_event_visible($event, $student->id));
481 }
482
294dce67 483 public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_teacher() {
757d5b7c
AN
484 $this->resetAfterTest();
485 $course = $this->getDataGenerator()->create_course();
486 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
487 $assign = $this->create_instance($course);
294dce67
MN
488
489 // Create a calendar event.
757d5b7c
AN
490 $this->setAdminUser();
491 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
294dce67 492
757d5b7c
AN
493 // The teacher should see the due date event.
494 $this->setUser($teacher);
294dce67
MN
495 $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
496 }
497
fda4374a
SR
498
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);
504
505 // Create a calendar event.
506 $this->setAdminUser();
507 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
508
509 // Now, log out.
510 $this->setUser();
511
512 // The teacher should see the due date event.
513 $this->assertTrue(mod_assign_core_calendar_is_event_visible($event, $teacher->id));
514 }
515
294dce67 516 public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_student() {
757d5b7c
AN
517 $this->resetAfterTest();
518 $course = $this->getDataGenerator()->create_course();
519 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
520 $assign = $this->create_instance($course);
294dce67
MN
521
522 // Create a calendar event.
757d5b7c
AN
523 $this->setAdminUser();
524 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
294dce67 525
757d5b7c
AN
526 // The student should not see the due date event.
527 $this->setUser($student);
294dce67
MN
528 $this->assertFalse(mod_assign_core_calendar_is_event_visible($event));
529 }
530
fda4374a
SR
531
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);
537
538 // Create a calendar event.
539 $this->setAdminUser();
540 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
541
542 // Now, log out.
543 $this->setUser();
544
545 // The student should not see the due date event.
546 $this->assertFalse(mod_assign_core_calendar_is_event_visible($event, $student->id));
547 }
548
294dce67 549 public function test_assign_core_calendar_provide_event_action_duedate_as_teacher() {
757d5b7c
AN
550 $this->resetAfterTest();
551 $course = $this->getDataGenerator()->create_course();
552 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
553 $assign = $this->create_instance($course);
294dce67
MN
554
555 // Create a calendar event.
757d5b7c
AN
556 $this->setAdminUser();
557 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
294dce67 558
757d5b7c
AN
559 // The teacher should see the event.
560 $this->setUser($teacher);
294dce67 561 $factory = new \core_calendar\action_factory();
294dce67
MN
562 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
563
7ec6d873
RW
564 // The teacher should not have an action for a due date event.
565 $this->assertNull($actionevent);
294dce67
MN
566 }
567
fda4374a
SR
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);
573
574 // Create a calendar event.
575 $this->setAdminUser();
576 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
577
578 // Now, log out.
579 $this->setUser();
580
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);
584
585 // The teacher should not have an action for a due date event.
586 $this->assertNull($actionevent);
587 }
588
294dce67 589 public function test_assign_core_calendar_provide_event_action_duedate_as_student() {
757d5b7c
AN
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]);
294dce67
MN
594
595 // Create a calendar event.
757d5b7c
AN
596 $this->setAdminUser();
597 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
294dce67 598
757d5b7c
AN
599 // The student should see the event.
600 $this->setUser($student);
294dce67 601 $factory = new \core_calendar\action_factory();
294dce67
MN
602 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
603
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 }
611
fda4374a
SR
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]);
617
618 // Create a calendar event.
619 $this->setAdminUser();
620 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
621
622 // Now, log out.
623 $this->setUser();
624
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);
628
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 }
636
294dce67 637 public function test_assign_core_calendar_provide_event_action_gradingduedate_as_teacher() {
757d5b7c
AN
638 $this->resetAfterTest();
639 $course = $this->getDataGenerator()->create_course();
640 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
641 $assign = $this->create_instance($course);
294dce67
MN
642
643 // Create a calendar event.
757d5b7c
AN
644 $this->setAdminUser();
645 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
294dce67 646
757d5b7c 647 $this->setUser($teacher);
294dce67 648 $factory = new \core_calendar\action_factory();
294dce67
MN
649 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
650
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 }
658
fda4374a
SR
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);
664
665 // Create a calendar event.
666 $this->setAdminUser();
667 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
668
669 // Now, log out.
670 $this->setUser();
671
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);
675
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 }
683
294dce67 684 public function test_assign_core_calendar_provide_event_action_gradingduedate_as_student() {
757d5b7c
AN
685 $this->resetAfterTest();
686 $course = $this->getDataGenerator()->create_course();
687 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
688 $assign = $this->create_instance($course);
294dce67
MN
689
690 // Create a calendar event.
757d5b7c
AN
691 $this->setAdminUser();
692 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
294dce67 693
757d5b7c 694 $this->setUser($student);
294dce67 695 $factory = new \core_calendar\action_factory();
294dce67
MN
696 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
697
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 }
705
fda4374a
SR
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);
711
712 // Create a calendar event.
713 $this->setAdminUser();
714 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
715
716 // Now, log out.
717 $this->setUser();
718
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);
722
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 }
730
d846cb24 731 public function test_assign_core_calendar_provide_event_action_duedate_as_student_submitted() {
757d5b7c
AN
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]);
d846cb24 737
757d5b7c 738 $this->setAdminUser();
d846cb24
AN
739
740 // Create a calendar event.
757d5b7c 741 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
d846cb24
AN
742
743 // Create an action factory.
744 $factory = new \core_calendar\action_factory();
745
757d5b7c
AN
746 // Submit as the student.
747 $this->add_submission($student, $assign);
748 $this->submit_for_grading($student, $assign);
d846cb24 749
757d5b7c 750 // Confirm there was no event to action.
d846cb24 751 $factory = new \core_calendar\action_factory();
d846cb24 752 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
d846cb24
AN
753 $this->assertNull($actionevent);
754 }
755
fda4374a
SR
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]);
762
763 $this->setAdminUser();
764
765 // Create a calendar event.
766 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
767
768 // Create an action factory.
769 $factory = new \core_calendar\action_factory();
770
771 // Submit as the student.
772 $this->add_submission($student, $assign);
773 $this->submit_for_grading($student, $assign);
774
775 // Now, log out.
776 $this->setUser();
777
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 }
783
294dce67
MN
784 /**
785 * Creates an action event.
786 *
757d5b7c
AN
787 * @param \stdClass $course The course the assignment is in
788 * @param assign $assign The assignment to create an event for
294dce67 789 * @param string $eventtype The event type. eg. ASSIGN_EVENT_TYPE_DUE.
e1cd93ce 790 * @return bool|calendar_event
294dce67 791 */
757d5b7c 792 private function create_action_event($course, $assign, $eventtype) {
294dce67
MN
793 $event = new stdClass();
794 $event->name = 'Calendar event';
795 $event->modulename = 'assign';
757d5b7c
AN
796 $event->courseid = $course->id;
797 $event->instance = $assign->get_instance()->id;
294dce67
MN
798 $event->type = CALENDAR_EVENT_TYPE_ACTION;
799 $event->eventtype = $eventtype;
800 $event->timestart = time();
801
e1cd93ce 802 return calendar_event::create($event);
294dce67 803 }
8db355c5
JD
804
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();
757d5b7c
AN
812 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
813
8db355c5
JD
814 $this->setAdminUser();
815
816 // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
757d5b7c
AN
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();
8db355c5
JD
819
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.
757d5b7c
AN
823 $moddefaults = (object) [
824 'customdata' => [
825 'customcompletionrules' => [
826 'completionsubmit' => '1',
827 ],
828 ],
829 'completion' => 2,
830 ];
8db355c5
JD
831
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 }
e04be435
JO
838
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();
757d5b7c
AN
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');
e04be435 848
757d5b7c
AN
849 // As a teacher.
850 $this->setUser($teacher);
851 $assign = $this->create_instance($course);
e04be435
JO
852
853 // Grade the student.
854 $data = ['grade' => 50];
757d5b7c 855 $assign->testable_apply_grade_to_user((object)$data, $student->id, 0);
e04be435 856
2d1075e3 857 // Try getting another students grade. This will give a grade of ASSIGN_GRADE_NOT_SET (-1).
757d5b7c 858 $assign->get_user_grade($otherstudent->id, true);
e04be435
JO
859
860 // Rescale.
757d5b7c 861 assign_rescale_activity_grades($course, $assign->get_course_module(), 0, 100, 0, 10);
e04be435
JO
862
863 // Get the grades for both students.
757d5b7c
AN
864 $studentgrade = $assign->get_user_grade($student->id, true);
865 $otherstudentgrade = $assign->get_user_grade($otherstudent->id, true);
e04be435 866
2d1075e3 867 // Make sure the real grade is scaled, but the ASSIGN_GRADE_NOT_SET stays the same.
757d5b7c
AN
868 $this->assertEquals($studentgrade->grade, 5);
869 $this->assertEquals($otherstudentgrade->grade, ASSIGN_GRADE_NOT_SET);
e04be435 870 }
f4c21561
RW
871
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');
878
879 $this->resetAfterTest();
757d5b7c
AN
880 $course = $this->getDataGenerator()->create_course();
881 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
882
f4c21561
RW
883 $this->setAdminUser();
884
f4c21561 885 $duedate = time();
757d5b7c 886 $assign = $this->create_instance($course, ['duedate' => $duedate]);
f4c21561
RW
887
888 $instance = $assign->get_instance();
889 $event = new \calendar_event((object)[
890 'modulename' => 'assign',
891 'instance' => $instance->id,
757d5b7c 892 'userid' => $student->id,
f4c21561
RW
893 ]);
894
895 $this->assertFalse($assign->is_override_calendar_event($event));
896 }
897
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');
904
905 $this->resetAfterTest();
757d5b7c
AN
906 $course = $this->getDataGenerator()->create_course();
907 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
908
f4c21561
RW
909 $this->setAdminUser();
910
757d5b7c 911 $userid = $student->id;
f4c21561 912 $duedate = time();
757d5b7c 913 $assign = $this->create_instance($course, ['duedate' => $duedate]);
f4c21561
RW
914
915 $instance = $assign->get_instance();
916 $event = new \calendar_event((object)[
917 'userid' => $userid
918 ]);
919
920 $this->assertFalse($assign->is_override_calendar_event($event));
921 }
922
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');
930
931 $this->resetAfterTest();
757d5b7c
AN
932 $course = $this->getDataGenerator()->create_course();
933 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
934
f4c21561
RW
935 $this->setAdminUser();
936
f4c21561 937 $duedate = time();
757d5b7c 938 $assign = $this->create_instance($course, ['duedate' => $duedate]);
f4c21561 939 $instance = $assign->get_instance();
757d5b7c
AN
940
941 $otherassign = $this->create_instance($course, ['duedate' => $duedate]);
942 $otherinstance = $otherassign->get_instance();
943
f4c21561
RW
944 $event = new \calendar_event((object) [
945 'modulename' => 'assign',
946 'instance' => $instance->id,
757d5b7c 947 'userid' => $student->id,
f4c21561
RW
948 ]);
949
757d5b7c
AN
950 $DB->insert_record('assign_overrides', (object) [
951 'assignid' => $otherinstance->id,
952 'userid' => $student->id,
953 ]);
f4c21561
RW
954
955 $this->assertFalse($assign->is_override_calendar_event($event));
956 }
957
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');
964
965 $this->resetAfterTest();
757d5b7c
AN
966 $course = $this->getDataGenerator()->create_course();
967 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
968
f4c21561
RW
969 $this->setAdminUser();
970
f4c21561 971 $duedate = time();
757d5b7c 972 $assign = $this->create_instance($course, ['duedate' => $duedate]);
f4c21561
RW
973
974 $instance = $assign->get_instance();
975 $event = new \calendar_event((object) [
976 'modulename' => 'assign',
977 'instance' => $instance->id,
757d5b7c 978 'userid' => $student->id,
f4c21561
RW
979 ]);
980
f4c21561 981
757d5b7c
AN
982 $DB->insert_record('assign_overrides', (object) [
983 'assignid' => $instance->id,
984 'userid' => $student->id,
985 ]);
f4c21561
RW
986
987 $this->assertTrue($assign->is_override_calendar_event($event));
988 }
989
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');
996
997 $this->resetAfterTest();
757d5b7c
AN
998 $course = $this->getDataGenerator()->create_course();
999
f4c21561
RW
1000 $this->setAdminUser();
1001
1002 $duedate = time();
757d5b7c 1003 $assign = $this->create_instance($course, ['duedate' => $duedate]);
f4c21561
RW
1004 $instance = $assign->get_instance();
1005 $group = $this->getDataGenerator()->create_group(array('courseid' => $instance->course));
f4c21561
RW
1006
1007 $event = new \calendar_event((object) [
1008 'modulename' => 'assign',
1009 'instance' => $instance->id,
757d5b7c 1010 'groupid' => $group->id,
f4c21561
RW
1011 ]);
1012
757d5b7c
AN
1013 $DB->insert_record('assign_overrides', (object) [
1014 'assignid' => $instance->id,
1015 'groupid' => $group->id,
1016 ]);
f4c21561
RW
1017
1018 $this->assertTrue($assign->is_override_calendar_event($event));
1019 }
1020
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');
1027
1028 $this->resetAfterTest();
757d5b7c
AN
1029 $course = $this->getDataGenerator()->create_course();
1030
f4c21561
RW
1031 $this->setAdminUser();
1032
1033 $duedate = time();
757d5b7c 1034 $assign = $this->create_instance($course, ['duedate' => $duedate]);
f4c21561
RW
1035 $instance = $assign->get_instance();
1036
1037 $event = new \calendar_event((object) [
1038 'courseid' => $instance->course,
1039 'modulename' => 'assign',
1040 'instance' => $instance->id,
1041 'eventtype' => 'SOME RANDOM EVENT'
1042 ]);
1043
478b1d19 1044 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
f4c21561
RW
1045 $this->assertNull($min);
1046 $this->assertNull($max);
1047 }
1048
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');
1055
1056 $this->resetAfterTest();
757d5b7c
AN
1057 $course = $this->getDataGenerator()->create_course();
1058 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1059
f4c21561
RW
1060 $this->setAdminUser();
1061
1062 $duedate = time();
757d5b7c 1063 $assign = $this->create_instance($course, ['duedate' => $duedate]);
f4c21561 1064 $instance = $assign->get_instance();
f4c21561
RW
1065
1066 $event = new \calendar_event((object) [
1067 'courseid' => $instance->course,
1068 'modulename' => 'assign',
1069 'instance' => $instance->id,
757d5b7c 1070 'userid' => $student->id,
f4c21561
RW
1071 'eventtype' => ASSIGN_EVENT_TYPE_DUE
1072 ]);
1073
1074 $record = (object) [
1075 'assignid' => $instance->id,
757d5b7c 1076 'userid' => $student->id,
f4c21561
RW
1077 ];
1078
1079 $DB->insert_record('assign_overrides', $record);
1080
478b1d19 1081 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
6688ae2b
RW
1082 $this->assertFalse($min);
1083 $this->assertFalse($max);
f4c21561
RW
1084 }
1085
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');
1093
1094 $this->resetAfterTest();
757d5b7c
AN
1095 $course = $this->getDataGenerator()->create_course();
1096
f4c21561
RW
1097 $this->setAdminUser();
1098
1099 $duedate = time();
757d5b7c 1100 $assign = $this->create_instance($course, [
f4c21561
RW
1101 'duedate' => $duedate,
1102 'allowsubmissionsfromdate' => 0,
1103 'cutoffdate' => 0,
1104 ]);
1105 $instance = $assign->get_instance();
f4c21561
RW
1106
1107 $event = new \calendar_event((object) [
1108 'courseid' => $instance->course,
1109 'modulename' => 'assign',
1110 'instance' => $instance->id,
1111 'eventtype' => ASSIGN_EVENT_TYPE_DUE
1112 ]);
1113
478b1d19 1114 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
f4c21561
RW
1115 $this->assertNull($min);
1116 $this->assertNull($max);
1117 }
1118
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');
1126
1127 $this->resetAfterTest();
757d5b7c
AN
1128 $course = $this->getDataGenerator()->create_course();
1129
f4c21561
RW
1130 $this->setAdminUser();
1131
1132 $duedate = time();
1133 $submissionsfromdate = $duedate - DAYSECS;
1134 $cutoffdate = $duedate + DAYSECS;
757d5b7c 1135 $assign = $this->create_instance($course, [
f4c21561
RW
1136 'duedate' => $duedate,
1137 'allowsubmissionsfromdate' => $submissionsfromdate,
1138 'cutoffdate' => $cutoffdate,
1139 ]);
1140 $instance = $assign->get_instance();
f4c21561
RW
1141
1142 $event = new \calendar_event((object) [
1143 'courseid' => $instance->course,
1144 'modulename' => 'assign',
1145 'instance' => $instance->id,
1146 'eventtype' => ASSIGN_EVENT_TYPE_DUE
1147 ]);
1148
478b1d19 1149 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
f4c21561
RW
1150 $this->assertEquals($submissionsfromdate, $min[0]);
1151 $this->assertNotEmpty($min[1]);
1152 $this->assertEquals($cutoffdate, $max[0]);
1153 $this->assertNotEmpty($max[1]);
1154 }
1155
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');
1162
1163 $this->resetAfterTest();
757d5b7c
AN
1164 $course = $this->getDataGenerator()->create_course();
1165
f4c21561
RW
1166 $this->setAdminUser();
1167
757d5b7c 1168 $assign = $this->create_instance($course, [
f4c21561
RW
1169 'duedate' => 0,
1170 'allowsubmissionsfromdate' => 0,
1171 'cutoffdate' => 0,
1172 ]);
1173 $instance = $assign->get_instance();
1174
1175 $event = new \calendar_event((object) [
1176 'courseid' => $instance->course,
1177 'modulename' => 'assign',
1178 'instance' => $instance->id,
1179 'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
1180 ]);
1181
478b1d19 1182 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
f4c21561
RW
1183 $this->assertNull($min);
1184 $this->assertNull($max);
1185 }
1186
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');
1193
1194 $this->resetAfterTest();
757d5b7c
AN
1195 $course = $this->getDataGenerator()->create_course();
1196
f4c21561
RW
1197 $this->setAdminUser();
1198
1199 $duedate = time();
757d5b7c 1200 $assign = $this->create_instance($course, ['duedate' => $duedate]);
f4c21561 1201 $instance = $assign->get_instance();
f4c21561
RW
1202
1203 $event = new \calendar_event((object) [
1204 'courseid' => $instance->course,
1205 'modulename' => 'assign',
1206 'instance' => $instance->id,
1207 'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
1208 ]);
1209
478b1d19 1210 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
f4c21561
RW
1211 $this->assertEquals($duedate, $min[0]);
1212 $this->assertNotEmpty($min[1]);
1213 $this->assertNull($max);
1214 }
1215
f4c21561
RW
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');
1222
1223 $this->resetAfterTest();
757d5b7c
AN
1224 $course = $this->getDataGenerator()->create_course();
1225 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1226
f4c21561
RW
1227 $this->setAdminUser();
1228
1229 $duedate = time();
1230 $submissionsfromdate = $duedate - DAYSECS;
1231 $cutoffdate = $duedate + DAYSECS;
757d5b7c 1232 $assign = $this->create_instance($course, [
f4c21561
RW
1233 'duedate' => $duedate,
1234 'allowsubmissionsfromdate' => $submissionsfromdate,
1235 'cutoffdate' => $cutoffdate,
1236 ]);
1237 $instance = $assign->get_instance();
1238
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 ]);
1246
478b1d19 1247 mod_assign_core_calendar_event_timestart_updated($event, $instance);
f4c21561
RW
1248
1249 $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1250 $this->assertEquals($duedate, $newinstance->duedate);
1251 }
1252
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');
1259
1260 $this->resetAfterTest();
757d5b7c
AN
1261 $course = $this->getDataGenerator()->create_course();
1262 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1263
f4c21561
RW
1264 $this->setAdminUser();
1265
1266 $duedate = time();
1267 $submissionsfromdate = $duedate - DAYSECS;
1268 $cutoffdate = $duedate + DAYSECS;
757d5b7c 1269 $assign = $this->create_instance($course, [
f4c21561
RW
1270 'duedate' => $duedate,
1271 'allowsubmissionsfromdate' => $submissionsfromdate,
1272 'cutoffdate' => $cutoffdate,
1273 ]);
1274 $instance = $assign->get_instance();
f4c21561
RW
1275
1276 $event = new \calendar_event((object) [
1277 'courseid' => $instance->course,
1278 'modulename' => 'assign',
1279 'instance' => $instance->id,
757d5b7c 1280 'userid' => $student->id,
f4c21561
RW
1281 'eventtype' => ASSIGN_EVENT_TYPE_DUE,
1282 'timestart' => $duedate + 1
1283 ]);
1284
1285 $record = (object) [
1286 'assignid' => $instance->id,
757d5b7c
AN
1287 'userid' => $student->id,
1288 'duedate' => $duedate + 1,
f4c21561
RW
1289 ];
1290
1291 $DB->insert_record('assign_overrides', $record);
1292
478b1d19 1293 mod_assign_core_calendar_event_timestart_updated($event, $instance);
f4c21561
RW
1294
1295 $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1296 $this->assertEquals($duedate, $newinstance->duedate);
1297 }
1298
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');
1305
1306 $this->resetAfterTest();
757d5b7c
AN
1307 $course = $this->getDataGenerator()->create_course();
1308 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1309
f4c21561
RW
1310 $this->setAdminUser();
1311
1312 $duedate = time();
1313 $newduedate = $duedate + 1;
1314 $submissionsfromdate = $duedate - DAYSECS;
1315 $cutoffdate = $duedate + DAYSECS;
757d5b7c 1316 $assign = $this->create_instance($course, [
f4c21561
RW
1317 'duedate' => $duedate,
1318 'allowsubmissionsfromdate' => $submissionsfromdate,
1319 'cutoffdate' => $cutoffdate,
1320 ]);
1321 $instance = $assign->get_instance();
1322
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 ]);
1330
478b1d19 1331 mod_assign_core_calendar_event_timestart_updated($event, $instance);
f4c21561
RW
1332
1333 $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1334 $this->assertEquals($newduedate, $newinstance->duedate);
1335 }
1336
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');
1345
1346 $this->resetAfterTest();
757d5b7c
AN
1347 $course = $this->getDataGenerator()->create_course();
1348 $context = context_course::instance($course->id);
1349
1350 $roleid = $this->getDataGenerator()->create_role();
1351 $role = $DB->get_record('role', ['id' => $roleid]);
1352 $user = $this->getDataGenerator()->create_and_enrol($course, $role->shortname);
1353
f4c21561
RW
1354 $this->setAdminUser();
1355
1356 $mapper = calendar_event_container::get_event_mapper();
f4c21561
RW
1357 $now = time();
1358 $duedate = (new DateTime())->setTimestamp($now);
1359 $newduedate = (new DateTime())->setTimestamp($now)->modify('+1 day');
757d5b7c 1360 $assign = $this->create_instance($course, [
f4c21561
RW
1361 'course' => $course->id,
1362 'duedate' => $duedate->getTimestamp(),
1363 ]);
1364 $instance = $assign->get_instance();
1365
f4c21561
RW
1366 $record = $DB->get_record('event', [
1367 'courseid' => $course->id,
1368 'modulename' => 'assign',
1369 'instance' => $instance->id,
1370 'eventtype' => ASSIGN_EVENT_TYPE_DUE
1371 ]);
1372
1373 $event = new \calendar_event($record);
1374
1375 assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1376 assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true);
1377
1378 $this->setUser($user);
1379
1380 calendar_local_api::update_event_start_day(
1381 $mapper->from_legacy_event_to_event($event),
1382 $newduedate
1383 );
1384
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 }
1392
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');
1401
1402 $this->resetAfterTest();
757d5b7c
AN
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']);
1407
f4c21561
RW
1408 $this->setAdminUser();
1409
1410 $mapper = calendar_event_container::get_event_mapper();
f4c21561
RW
1411 $now = time();
1412 $duedate = (new DateTime())->setTimestamp($now);
1413 $newduedate = (new DateTime())->setTimestamp($now)->modify('+1 day');
757d5b7c 1414 $assign = $this->create_instance($course, [
f4c21561
RW
1415 'course' => $course->id,
1416 'duedate' => $duedate->getTimestamp(),
1417 ]);
1418 $instance = $assign->get_instance();
1419
f4c21561
RW
1420 $record = $DB->get_record('event', [
1421 'courseid' => $course->id,
1422 'modulename' => 'assign',
1423 'instance' => $instance->id,
1424 'eventtype' => ASSIGN_EVENT_TYPE_DUE
1425 ]);
1426
1427 $event = new \calendar_event($record);
1428
1429 assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1430 assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true);
1431
1432 $this->setUser($user);
1433 // Trigger and capture the event when adding a contact.
1434 $sink = $this->redirectEvents();
1435
1436 calendar_local_api::update_event_start_day(
1437 $mapper->from_legacy_event_to_event($event),
1438 $newduedate
1439 );
1440
1441 $triggeredevents = $sink->get_events();
1442 $moduleupdatedevents = array_filter($triggeredevents, function($e) {
1443 return is_a($e, 'core\event\course_module_updated');
1444 });
1445
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 }
cb5b4860 1456}