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