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