See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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/locallib.php. 19 * 20 * @package mod_assign 21 * @category phpunit 22 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 global $CFG; 30 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 31 require_once($CFG->dirroot . '/mod/assign/upgradelib.php'); 32 require_once($CFG->dirroot . '/mod/assign/tests/generator.php'); 33 34 /** 35 * Unit tests for (some of) mod/assign/locallib.php. 36 * 37 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class mod_assign_locallib_testcase extends advanced_testcase { 41 42 // Use the generator helper. 43 use mod_assign_test_generator; 44 45 public function test_return_links() { 46 global $PAGE; 47 48 $this->resetAfterTest(); 49 $course = $this->getDataGenerator()->create_course(); 50 51 $assign = $this->create_instance($course); 52 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 53 54 $assign->register_return_link('RETURNACTION', ['param' => 1]); 55 $this->assertEquals('RETURNACTION', $assign->get_return_action()); 56 $this->assertEquals(['param' => 1], $assign->get_return_params()); 57 } 58 59 public function test_get_feedback_plugins() { 60 $this->resetAfterTest(); 61 $course = $this->getDataGenerator()->create_course(); 62 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 63 64 $this->setUser($teacher); 65 $assign = $this->create_instance($course); 66 $installedplugins = array_keys(core_component::get_plugin_list('assignfeedback')); 67 68 foreach ($assign->get_feedback_plugins() as $plugin) { 69 $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins'); 70 } 71 } 72 73 public function test_get_submission_plugins() { 74 $this->resetAfterTest(); 75 $course = $this->getDataGenerator()->create_course(); 76 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 77 78 $this->setUser($teacher); 79 $assign = $this->create_instance($course); 80 $installedplugins = array_keys(core_component::get_plugin_list('assignsubmission')); 81 82 foreach ($assign->get_submission_plugins() as $plugin) { 83 $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins'); 84 } 85 } 86 87 public function test_is_blind_marking() { 88 $this->resetAfterTest(); 89 $course = $this->getDataGenerator()->create_course(); 90 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 91 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 92 93 $this->setUser($teacher); 94 $assign = $this->create_instance($course, ['blindmarking' => 1]); 95 $this->assertEquals(true, $assign->is_blind_marking()); 96 97 // Test cannot see student names. 98 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 99 $output = $assign->get_renderer()->render($gradingtable); 100 $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign'))); 101 102 // Test students cannot reveal identities. 103 $nopermission = false; 104 $student->ignoresesskey = true; 105 $this->setUser($student); 106 $this->expectException('required_capability_exception'); 107 $assign->reveal_identities(); 108 $student->ignoresesskey = false; 109 110 // Test teachers cannot reveal identities. 111 $nopermission = false; 112 $teacher->ignoresesskey = true; 113 $this->setUser($teacher); 114 $this->expectException('required_capability_exception'); 115 $assign->reveal_identities(); 116 $teacher->ignoresesskey = false; 117 118 // Test sesskey is required. 119 $this->setUser($teacher); 120 $this->expectException('moodle_exception'); 121 $assign->reveal_identities(); 122 123 // Test editingteacher can reveal identities if sesskey is ignored. 124 $teacher->ignoresesskey = true; 125 $this->setUser($teacher); 126 $assign->reveal_identities(); 127 $this->assertEquals(false, $assign->is_blind_marking()); 128 $teacher->ignoresesskey = false; 129 130 // Test student names are visible. 131 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 132 $output = $assign->get_renderer()->render($gradingtable); 133 $this->assertEquals(false, strpos($output, get_string('hiddenuser', 'assign'))); 134 135 // Set this back to default. 136 $teacher->ignoresesskey = false; 137 } 138 139 /** 140 * Data provider for test_get_assign_perpage 141 * 142 * @return array Provider data 143 */ 144 public function get_assign_perpage_provider() { 145 return array( 146 array( 147 'maxperpage' => -1, 148 'userprefs' => array( 149 -1 => -1, 150 10 => 10, 151 20 => 20, 152 50 => 50, 153 ), 154 ), 155 array( 156 'maxperpage' => 15, 157 'userprefs' => array( 158 -1 => 15, 159 10 => 10, 160 20 => 15, 161 50 => 15, 162 ), 163 ), 164 ); 165 } 166 167 /** 168 * Test maxperpage 169 * 170 * @dataProvider get_assign_perpage_provider 171 * @param integer $maxperpage site config value 172 * @param array $userprefs Array of user preferences and expected page sizes 173 */ 174 public function test_get_assign_perpage($maxperpage, $userprefs) { 175 $this->resetAfterTest(); 176 $course = $this->getDataGenerator()->create_course(); 177 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 178 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 179 180 $this->setUser($teacher); 181 $assign = $this->create_instance($course); 182 183 set_config('maxperpage', $maxperpage, 'assign'); 184 set_user_preference('assign_perpage', null); 185 $this->assertEquals(10, $assign->get_assign_perpage()); 186 foreach ($userprefs as $pref => $perpage) { 187 set_user_preference('assign_perpage', $pref); 188 $this->assertEquals($perpage, $assign->get_assign_perpage()); 189 } 190 } 191 192 /** 193 * Test filter by requires grading. 194 * 195 * This is specifically checking an assignment with no grade to make sure we do not 196 * get an exception thrown when rendering the grading table for this type of assignment. 197 */ 198 public function test_gradingtable_filter_by_requiresgrading_no_grade() { 199 global $PAGE; 200 201 $this->resetAfterTest(); 202 203 $course = $this->getDataGenerator()->create_course(); 204 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 205 $this->setUser($teacher); 206 $assign = $this->create_instance($course, [ 207 'assignsubmission_onlinetext_enabled' => 1, 208 'assignfeedback_comments_enabled' => 0, 209 'grade' => GRADE_TYPE_NONE 210 ]); 211 212 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array( 213 'id' => $assign->get_course_module()->id, 214 'action' => 'grading', 215 ))); 216 217 // Render the table with the requires grading filter. 218 $gradingtable = new assign_grading_table($assign, 1, ASSIGN_FILTER_REQUIRE_GRADING, 0, true); 219 $output = $assign->get_renderer()->render($gradingtable); 220 221 // Test that the filter function does not throw errors for assignments with no grade. 222 $this->assertContains(get_string('nothingtodisplay'), $output); 223 } 224 225 226 /** 227 * Test submissions with extension date. 228 */ 229 public function test_gradingtable_extension_due_date() { 230 global $PAGE; 231 232 $this->resetAfterTest(); 233 $course = $this->getDataGenerator()->create_course(); 234 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 235 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 236 237 // Setup the assignment. 238 $this->setUser($teacher); 239 $time = time(); 240 $assign = $this->create_instance($course, [ 241 'assignsubmission_onlinetext_enabled' => 1, 242 'duedate' => time() - (4 * DAYSECS), 243 ]); 244 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array( 245 'id' => $assign->get_course_module()->id, 246 'action' => 'grading', 247 ))); 248 249 // Check that the assignment is late. 250 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 251 $output = $assign->get_renderer()->render($gradingtable); 252 $this->assertContains(get_string('submissionstatus_', 'assign'), $output); 253 $this->assertContains(get_string('overdue', 'assign', format_time((4 * DAYSECS))), $output); 254 255 // Grant an extension. 256 $extendedtime = $time + (2 * DAYSECS); 257 $assign->testable_save_user_extension($student->id, $extendedtime); 258 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 259 $output = $assign->get_renderer()->render($gradingtable); 260 $this->assertContains(get_string('submissionstatus_', 'assign'), $output); 261 $this->assertContains(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output); 262 263 // Simulate a submission. 264 $this->setUser($student); 265 $submission = $assign->get_user_submission($student->id, true); 266 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 267 $assign->testable_update_submission($submission, $student->id, true, false); 268 $data = new stdClass(); 269 $data->onlinetext_editor = [ 270 'itemid' => file_get_unused_draft_itemid(), 271 'text' => 'Submission text', 272 'format' => FORMAT_MOODLE, 273 ]; 274 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 275 $plugin->save($submission, $data); 276 277 // Verify output. 278 $this->setUser($teacher); 279 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 280 $output = $assign->get_renderer()->render($gradingtable); 281 $this->assertContains(get_string('submissionstatus_submitted', 'assign'), $output); 282 $this->assertContains(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output); 283 } 284 285 /** 286 * Test that late submissions with extension date calculate correctly. 287 */ 288 public function test_gradingtable_extension_date_calculation_for_lateness() { 289 global $PAGE; 290 291 $this->resetAfterTest(); 292 $course = $this->getDataGenerator()->create_course(); 293 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 294 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 295 296 // Setup the assignment. 297 $this->setUser($teacher); 298 $time = time(); 299 $assign = $this->create_instance($course, [ 300 'assignsubmission_onlinetext_enabled' => 1, 301 'duedate' => time() - (4 * DAYSECS), 302 ]); 303 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array( 304 'id' => $assign->get_course_module()->id, 305 'action' => 'grading', 306 ))); 307 308 // Check that the assignment is late. 309 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 310 $output = $assign->get_renderer()->render($gradingtable); 311 $this->assertContains(get_string('submissionstatus_', 'assign'), $output); 312 $difftime = time() - $time; 313 $this->assertContains(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output); 314 315 // Grant an extension that is in the past. 316 $assign->testable_save_user_extension($student->id, $time - (2 * DAYSECS)); 317 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 318 $output = $assign->get_renderer()->render($gradingtable); 319 $this->assertContains(get_string('submissionstatus_', 'assign'), $output); 320 $this->assertContains(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output); 321 $difftime = time() - $time; 322 $this->assertContains(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output); 323 324 // Simulate a submission. 325 $this->setUser($student); 326 $submission = $assign->get_user_submission($student->id, true); 327 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 328 $assign->testable_update_submission($submission, $student->id, true, false); 329 $data = new stdClass(); 330 $data->onlinetext_editor = [ 331 'itemid' => file_get_unused_draft_itemid(), 332 'text' => 'Submission text', 333 'format' => FORMAT_MOODLE, 334 ]; 335 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 336 $plugin->save($submission, $data); 337 $submittedtime = time(); 338 339 // Verify output. 340 $this->setUser($teacher); 341 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 342 $output = $assign->get_renderer()->render($gradingtable); 343 $this->assertContains(get_string('submissionstatus_submitted', 'assign'), $output); 344 $this->assertContains(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output); 345 346 $difftime = $submittedtime - $time; 347 $this->assertContains(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)), $output); 348 } 349 350 public function test_gradingtable_status_rendering() { 351 global $PAGE; 352 353 $this->resetAfterTest(); 354 $course = $this->getDataGenerator()->create_course(); 355 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 356 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 357 358 // Setup the assignment. 359 $this->setUser($teacher); 360 $time = time(); 361 $assign = $this->create_instance($course, [ 362 'assignsubmission_onlinetext_enabled' => 1, 363 'duedate' => $time - (4 * DAYSECS), 364 ]); 365 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array( 366 'id' => $assign->get_course_module()->id, 367 'action' => 'grading', 368 ))); 369 370 // Check that the assignment is late. 371 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 372 $output = $assign->get_renderer()->render($gradingtable); 373 $this->assertContains(get_string('submissionstatus_', 'assign'), $output); 374 $difftime = time() - $time; 375 $this->assertContains(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output); 376 377 // Simulate a student viewing the assignment without submitting. 378 $this->setUser($student); 379 $submission = $assign->get_user_submission($student->id, true); 380 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW; 381 $assign->testable_update_submission($submission, $student->id, true, false); 382 $submittedtime = time(); 383 384 // Verify output. 385 $this->setUser($teacher); 386 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 387 $output = $assign->get_renderer()->render($gradingtable); 388 $difftime = $submittedtime - $time; 389 $this->assertContains(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output); 390 391 $document = new DOMDocument(); 392 @$document->loadHTML($output); 393 $xpath = new DOMXPath($document); 394 $this->assertEquals('', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])')); 395 } 396 397 /** 398 * Check that group submission information is rendered correctly in the 399 * grading table. 400 */ 401 public function test_gradingtable_group_submissions_rendering() { 402 global $PAGE; 403 404 $this->resetAfterTest(); 405 $course = $this->getDataGenerator()->create_course(); 406 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 407 408 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 409 groups_add_member($group, $teacher); 410 411 $students = []; 412 413 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 414 $students[] = $student; 415 groups_add_member($group, $student); 416 417 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 418 $students[] = $student; 419 groups_add_member($group, $student); 420 421 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 422 $students[] = $student; 423 groups_add_member($group, $student); 424 425 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 426 $students[] = $student; 427 groups_add_member($group, $student); 428 429 // Verify group assignments. 430 $this->setUser($teacher); 431 $assign = $this->create_instance($course, [ 432 'teamsubmission' => 1, 433 'assignsubmission_onlinetext_enabled' => 1, 434 'submissiondrafts' => 1, 435 'requireallteammemberssubmit' => 0, 436 ]); 437 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array( 438 'id' => $assign->get_course_module()->id, 439 'action' => 'grading', 440 ))); 441 442 // Add a submission. 443 $this->setUser($student); 444 $data = new stdClass(); 445 $data->onlinetext_editor = [ 446 'itemid' => file_get_unused_draft_itemid(), 447 'text' => 'Submission text', 448 'format' => FORMAT_MOODLE, 449 ]; 450 $notices = array(); 451 $assign->save_submission($data, $notices); 452 453 $submission = $assign->get_group_submission($student->id, 0, true); 454 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 455 $assign->testable_update_submission($submission, $student->id, true, true); 456 457 // Check output. 458 $this->setUser($teacher); 459 $gradingtable = new assign_grading_table($assign, 4, '', 0, true); 460 $output = $assign->get_renderer()->render($gradingtable); 461 $document = new DOMDocument(); 462 @$document->loadHTML($output); 463 $xpath = new DOMXPath($document); 464 465 // Check status. 466 $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c4"]/div[@class="submissionstatussubmitted"])')); 467 $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c4"]/div[@class="submissionstatussubmitted"])')); 468 469 // Check submission last modified date 470 $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])'))); 471 $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c8"])'))); 472 473 // Check group. 474 $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c5"])')); 475 $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c5"])')); 476 477 // Check submission text. 478 $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c9"]/div/div)')); 479 $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c9"]/div/div)')); 480 481 // Check comments can be made. 482 $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r0_c10"]//textarea)')); 483 $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r3_c10"]//textarea)')); 484 } 485 486 public function test_show_intro() { 487 $this->resetAfterTest(); 488 $course = $this->getDataGenerator()->create_course(); 489 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 490 491 // Test whether we are showing the intro at the correct times. 492 $this->setUser($teacher); 493 $assign = $this->create_instance($course, ['alwaysshowdescription' => 1]); 494 495 $this->assertEquals(true, $assign->testable_show_intro()); 496 497 $tomorrow = time() + DAYSECS; 498 499 $assign = $this->create_instance($course, [ 500 'alwaysshowdescription' => 0, 501 'allowsubmissionsfromdate' => $tomorrow, 502 ]); 503 $this->assertEquals(false, $assign->testable_show_intro()); 504 $yesterday = time() - DAYSECS; 505 $assign = $this->create_instance($course, [ 506 'alwaysshowdescription' => 0, 507 'allowsubmissionsfromdate' => $yesterday, 508 ]); 509 $this->assertEquals(true, $assign->testable_show_intro()); 510 } 511 512 public function test_has_submissions_or_grades() { 513 $this->resetAfterTest(); 514 $course = $this->getDataGenerator()->create_course(); 515 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 516 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 517 518 $this->setUser($teacher); 519 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]); 520 $instance = $assign->get_instance(); 521 522 // Should start empty. 523 $this->assertEquals(false, $assign->has_submissions_or_grades()); 524 525 // Simulate a submission. 526 $this->setUser($student); 527 $submission = $assign->get_user_submission($student->id, true); 528 529 // The submission is still new. 530 $this->assertEquals(false, $assign->has_submissions_or_grades()); 531 532 // Submit the submission. 533 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 534 $assign->testable_update_submission($submission, $student->id, true, false); 535 $data = new stdClass(); 536 $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(), 537 'text'=>'Submission text', 538 'format'=>FORMAT_MOODLE); 539 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 540 $plugin->save($submission, $data); 541 542 // Now test again. 543 $this->assertEquals(true, $assign->has_submissions_or_grades()); 544 } 545 546 public function test_delete_grades() { 547 $this->resetAfterTest(); 548 $course = $this->getDataGenerator()->create_course(); 549 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 550 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 551 552 $this->setUser($teacher); 553 $assign = $this->create_instance($course); 554 555 // Simulate adding a grade. 556 $this->setUser($teacher); 557 $data = new stdClass(); 558 $data->grade = '50.0'; 559 $assign->testable_apply_grade_to_user($data, $student->id, 0); 560 561 // Now see if the data is in the gradebook. 562 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id); 563 564 $this->assertNotEquals(0, count($gradinginfo->items)); 565 566 $assign->testable_delete_grades(); 567 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id); 568 569 $this->assertEquals(0, count($gradinginfo->items)); 570 } 571 572 public function test_delete_instance() { 573 $this->resetAfterTest(); 574 $course = $this->getDataGenerator()->create_course(); 575 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 576 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 577 578 $this->setUser($teacher); 579 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]); 580 581 // Simulate adding a grade. 582 $this->setUser($teacher); 583 $data = new stdClass(); 584 $data->grade = '50.0'; 585 $assign->testable_apply_grade_to_user($data, $student->id, 0); 586 587 // Simulate a submission. 588 $this->add_submission($student, $assign); 589 590 // Now try and delete. 591 $this->setUser($teacher); 592 $this->assertEquals(true, $assign->delete_instance()); 593 } 594 595 public function test_reset_userdata() { 596 global $DB; 597 598 $this->resetAfterTest(); 599 $course = $this->getDataGenerator()->create_course(); 600 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 601 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 602 603 $now = time(); 604 $this->setUser($teacher); 605 $assign = $this->create_instance($course, [ 606 'assignsubmission_onlinetext_enabled' => 1, 607 'duedate' => $now, 608 ]); 609 610 // Simulate adding a grade. 611 $this->add_submission($student, $assign); 612 $this->submit_for_grading($student, $assign); 613 $this->mark_submission($teacher, $assign, $student, 50.0); 614 615 // Simulate a submission. 616 $this->setUser($student); 617 $submission = $assign->get_user_submission($student->id, true); 618 $data = new stdClass(); 619 $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(), 620 'text'=>'Submission text', 621 'format'=>FORMAT_MOODLE); 622 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 623 $plugin->save($submission, $data); 624 625 $this->assertEquals(true, $assign->has_submissions_or_grades()); 626 // Now try and reset. 627 $data = new stdClass(); 628 $data->reset_assign_submissions = 1; 629 $data->reset_gradebook_grades = 1; 630 $data->reset_assign_user_overrides = 1; 631 $data->reset_assign_group_overrides = 1; 632 $data->courseid = $course->id; 633 $data->timeshift = DAYSECS; 634 $this->setUser($teacher); 635 $assign->reset_userdata($data); 636 $this->assertEquals(false, $assign->has_submissions_or_grades()); 637 638 // Reload the instance data. 639 $instance = $DB->get_record('assign', array('id'=>$assign->get_instance()->id)); 640 $this->assertEquals($now + DAYSECS, $instance->duedate); 641 642 // Test reset using assign_reset_userdata(). 643 $assignduedate = $instance->duedate; // Keep old updated value for comparison. 644 $data->timeshift = (2 * DAYSECS); 645 assign_reset_userdata($data); 646 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id)); 647 $this->assertEquals($assignduedate + (2 * DAYSECS), $instance->duedate); 648 649 // Create one more assignment and reset, make sure time shifted for previous assignment is not changed. 650 $assign2 = $this->create_instance($course, [ 651 'assignsubmission_onlinetext_enabled' => 1, 652 'duedate' => $now, 653 ]); 654 $assignduedate = $instance->duedate; 655 $data->timeshift = 3*DAYSECS; 656 $assign2->reset_userdata($data); 657 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id)); 658 $this->assertEquals($assignduedate, $instance->duedate); 659 $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id)); 660 $this->assertEquals($now + 3*DAYSECS, $instance2->duedate); 661 662 // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date. 663 $assignduedate = $instance->duedate; 664 $assign2duedate = $instance2->duedate; 665 $data->timeshift = (4 * DAYSECS); 666 assign_reset_userdata($data); 667 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id)); 668 $this->assertEquals($assignduedate + (4 * DAYSECS), $instance->duedate); 669 $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id)); 670 $this->assertEquals($assign2duedate + (4 * DAYSECS), $instance2->duedate); 671 } 672 673 public function test_plugin_settings() { 674 global $DB; 675 676 $this->resetAfterTest(); 677 678 $course = $this->getDataGenerator()->create_course(); 679 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 680 681 $now = time(); 682 $this->setUser($teacher); 683 $assign = $this->create_instance($course, [ 684 'assignsubmission_file_enabled' => 1, 685 'assignsubmission_file_maxfiles' => 12, 686 'assignsubmission_file_maxsizebytes' => 10, 687 ]); 688 689 $plugin = $assign->get_submission_plugin_by_type('file'); 690 $this->assertEquals('12', $plugin->get_config('maxfilesubmissions')); 691 } 692 693 public function test_update_calendar() { 694 global $DB; 695 696 $this->resetAfterTest(); 697 698 $course = $this->getDataGenerator()->create_course(); 699 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 700 701 $this->setUser($teacher); 702 $userctx = context_user::instance($teacher->id)->id; 703 704 // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey. 705 $draftid = file_get_unused_draft_itemid(); 706 $_REQUEST['introeditor'] = $draftid; 707 $_POST['introeditor'] = $draftid; 708 $_POST['sesskey'] = sesskey(); 709 710 // Write links to a draft area. 711 $fakearealink1 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">link</a>', 'draftfile.php', $userctx, 712 'user', 'draft', $draftid); 713 $fakearealink2 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">new</a>', 'draftfile.php', $userctx, 714 'user', 'draft', $draftid); 715 716 // Create a new assignment with links to a draft area. 717 $now = time(); 718 $assign = $this->create_instance($course, [ 719 'duedate' => $now, 720 'intro' => $fakearealink1, 721 'introformat' => FORMAT_HTML 722 ]); 723 724 // See if there is an event in the calendar. 725 $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id); 726 $event = $DB->get_record('event', $params); 727 $this->assertNotEmpty($event); 728 $this->assertSame('link', $event->description); // The pluginfile links are removed. 729 730 // Make sure the same works when updating the assignment. 731 $instance = $assign->get_instance(); 732 $instance->instance = $instance->id; 733 $instance->intro = $fakearealink2; 734 $instance->introformat = FORMAT_HTML; 735 $assign->update_instance($instance); 736 $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id); 737 $event = $DB->get_record('event', $params); 738 $this->assertNotEmpty($event); 739 $this->assertSame('new', $event->description); // The pluginfile links are removed. 740 741 // Create an assignment with a description that should be hidden. 742 $assign = $this->create_instance($course, [ 743 'duedate' => $now + 160, 744 'alwaysshowdescription' => false, 745 'allowsubmissionsfromdate' => $now + 60, 746 'intro' => 'Some text', 747 ]); 748 749 // Get the event from the calendar. 750 $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id); 751 $event = $DB->get_record('event', [ 752 'modulename' => 'assign', 753 'instance' => $assign->get_instance()->id, 754 ]); 755 756 $this->assertEmpty($event->description); 757 758 // Change the allowsubmissionfromdate to the past - do this directly in the DB 759 // because if we call the assignment update method - it will update the calendar 760 // and we want to test that this works from cron. 761 $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id'=>$assign->get_instance()->id)); 762 // Run cron to update the event in the calendar. 763 assign::cron(); 764 $event = $DB->get_record('event', $params); 765 766 $this->assertContains('Some text', $event->description); 767 768 } 769 770 public function test_update_instance() { 771 global $DB; 772 773 $this->resetAfterTest(); 774 775 $course = $this->getDataGenerator()->create_course(); 776 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 777 778 $this->setUser($teacher); 779 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]); 780 781 $now = time(); 782 $instance = $assign->get_instance(); 783 $instance->duedate = $now; 784 $instance->instance = $instance->id; 785 $instance->assignsubmission_onlinetext_enabled = 1; 786 787 $assign->update_instance($instance); 788 789 $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]); 790 $this->assertEquals($now, $instance->duedate); 791 } 792 793 public function test_cannot_submit_empty() { 794 global $PAGE; 795 796 $this->resetAfterTest(); 797 798 $course = $this->getDataGenerator()->create_course(); 799 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 800 801 $assign = $this->create_instance($course, ['submissiondrafts' => 1]); 802 803 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 804 805 // Test you cannot see the submit button for an offline assignment regardless. 806 $this->setUser($student); 807 $output = $assign->view_student_summary($student, true); 808 $this->assertNotContains(get_string('submitassignment', 'assign'), $output, 'Can submit empty offline assignment'); 809 } 810 811 public function test_cannot_submit_empty_no_submission() { 812 global $PAGE; 813 814 $this->resetAfterTest(); 815 816 $course = $this->getDataGenerator()->create_course(); 817 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 818 819 $assign = $this->create_instance($course, [ 820 'submissiondrafts' => 1, 821 'assignsubmission_onlinetext_enabled' => 1, 822 ]); 823 824 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 825 826 // Test you cannot see the submit button for an online text assignment with no submission. 827 $this->setUser($student); 828 $output = $assign->view_student_summary($student, true); 829 $this->assertNotContains(get_string('submitassignment', 'assign'), $output, 'Cannot submit empty onlinetext assignment'); 830 } 831 832 public function test_can_submit_with_submission() { 833 global $PAGE; 834 835 $this->resetAfterTest(); 836 837 $course = $this->getDataGenerator()->create_course(); 838 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 839 840 $assign = $this->create_instance($course, [ 841 'submissiondrafts' => 1, 842 'assignsubmission_onlinetext_enabled' => 1, 843 ]); 844 845 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 846 847 // Add a draft. 848 $this->add_submission($student, $assign); 849 850 // Test you can see the submit button for an online text assignment with a submission. 851 $this->setUser($student); 852 $output = $assign->view_student_summary($student, true); 853 $this->assertContains(get_string('submitassignment', 'assign'), $output, 'Can submit non empty onlinetext assignment'); 854 } 855 856 /** 857 * Test new_submission_empty 858 * 859 * We only test combinations of plugins here. Individual plugins are tested 860 * in their respective test files. 861 * 862 * @dataProvider new_submission_empty_testcases 863 * @param string $data The file submission data 864 * @param bool $expected The expected return value 865 */ 866 public function test_new_submission_empty($data, $expected) { 867 $this->resetAfterTest(); 868 869 $course = $this->getDataGenerator()->create_course(); 870 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 871 872 $assign = $this->create_instance($course, [ 873 'assignsubmission_file_enabled' => 1, 874 'assignsubmission_file_maxfiles' => 12, 875 'assignsubmission_file_maxsizebytes' => 10, 876 'assignsubmission_onlinetext_enabled' => 1, 877 ]); 878 $this->setUser($student); 879 $submission = new stdClass(); 880 881 if ($data['file'] && isset($data['file']['filename'])) { 882 $itemid = file_get_unused_draft_itemid(); 883 $submission->files_filemanager = $itemid; 884 $data['file'] += ['contextid' => context_user::instance($student->id)->id, 'itemid' => $itemid]; 885 $fs = get_file_storage(); 886 $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']); 887 } 888 889 if ($data['onlinetext']) { 890 $submission->onlinetext_editor = ['text' => $data['onlinetext']]; 891 } 892 893 $result = $assign->new_submission_empty($submission); 894 $this->assertTrue($result === $expected); 895 } 896 897 /** 898 * Dataprovider for the test_new_submission_empty testcase 899 * 900 * @return array of testcases 901 */ 902 public function new_submission_empty_testcases() { 903 return [ 904 'With file and onlinetext' => [ 905 [ 906 'file' => [ 907 'component' => 'user', 908 'filearea' => 'draft', 909 'filepath' => '/', 910 'filename' => 'not_a_virus.exe' 911 ], 912 'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu' 913 ], 914 false 915 ] 916 ]; 917 } 918 919 public function test_list_participants() { 920 global $CFG; 921 922 $this->resetAfterTest(); 923 924 $course = $this->getDataGenerator()->create_course(); 925 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 926 927 // Create 10 students. 928 for ($i = 0; $i < 10; $i++) { 929 $this->getDataGenerator()->create_and_enrol($course, 'student'); 930 } 931 932 $this->setUser($teacher); 933 $assign = $this->create_instance($course, ['grade' => 100]); 934 935 $this->assertCount(10, $assign->list_participants(null, true)); 936 } 937 938 public function test_list_participants_activeenrol() { 939 global $CFG, $DB; 940 941 $this->resetAfterTest(); 942 943 $course = $this->getDataGenerator()->create_course(); 944 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 945 946 // Create 10 students. 947 for ($i = 0; $i < 10; $i++) { 948 $this->getDataGenerator()->create_and_enrol($course, 'student'); 949 } 950 951 // Create 10 suspended students. 952 for ($i = 0; $i < 10; $i++) { 953 $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 954 } 955 956 $this->setUser($teacher); 957 set_user_preference('grade_report_showonlyactiveenrol', false); 958 $assign = $this->create_instance($course, ['grade' => 100]); 959 960 $this->assertCount(10, $assign->list_participants(null, true)); 961 } 962 963 public function test_list_participants_with_group_restriction() { 964 global $CFG; 965 966 $this->resetAfterTest(); 967 968 $course = $this->getDataGenerator()->create_course(); 969 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 970 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 971 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 972 $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 973 974 // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group. 975 $CFG->enableavailability = true; 976 977 $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 978 $assign = $this->create_instance($course, [ 979 'grade' => 100, 980 'availability' => json_encode( 981 \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)]) 982 ), 983 ]); 984 985 groups_add_member($specialgroup, $student); 986 groups_add_member($specialgroup, $otherstudent); 987 $this->assertEquals(2, count($assign->list_participants(null, true))); 988 } 989 990 public function test_get_participant_user_not_exist() { 991 $this->resetAfterTest(); 992 $course = $this->getDataGenerator()->create_course(); 993 994 $assign = $this->create_instance($course); 995 $this->assertNull($assign->get_participant('-1')); 996 } 997 998 public function test_get_participant_not_enrolled() { 999 $this->resetAfterTest(); 1000 $course = $this->getDataGenerator()->create_course(); 1001 $assign = $this->create_instance($course); 1002 1003 $user = $this->getDataGenerator()->create_user(); 1004 $this->assertNull($assign->get_participant($user->id)); 1005 } 1006 1007 public function test_get_participant_no_submission() { 1008 $this->resetAfterTest(); 1009 $course = $this->getDataGenerator()->create_course(); 1010 $assign = $this->create_instance($course); 1011 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1012 1013 $participant = $assign->get_participant($student->id); 1014 1015 $this->assertEquals($student->id, $participant->id); 1016 $this->assertFalse($participant->submitted); 1017 $this->assertFalse($participant->requiregrading); 1018 $this->assertFalse($participant->grantedextension); 1019 } 1020 1021 public function test_get_participant_granted_extension() { 1022 $this->resetAfterTest(); 1023 $course = $this->getDataGenerator()->create_course(); 1024 $assign = $this->create_instance($course); 1025 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1026 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1027 1028 $this->setUser($teacher); 1029 $assign->save_user_extension($student->id, time()); 1030 $participant = $assign->get_participant($student->id); 1031 1032 $this->assertEquals($student->id, $participant->id); 1033 $this->assertFalse($participant->submitted); 1034 $this->assertFalse($participant->requiregrading); 1035 $this->assertTrue($participant->grantedextension); 1036 } 1037 1038 public function test_get_participant_with_ungraded_submission() { 1039 $this->resetAfterTest(); 1040 $course = $this->getDataGenerator()->create_course(); 1041 $assign = $this->create_instance($course); 1042 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1043 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1044 1045 // Simulate a submission. 1046 $this->add_submission($student, $assign); 1047 $this->submit_for_grading($student, $assign); 1048 1049 $participant = $assign->get_participant($student->id); 1050 1051 $this->assertEquals($student->id, $participant->id); 1052 $this->assertTrue($participant->submitted); 1053 $this->assertTrue($participant->requiregrading); 1054 $this->assertFalse($participant->grantedextension); 1055 } 1056 1057 /** 1058 * Tests that if a student with no submission who can no longer submit is not a participant. 1059 */ 1060 public function test_get_participant_with_no_submission_no_capability() { 1061 global $DB; 1062 $this->resetAfterTest(); 1063 $course = self::getDataGenerator()->create_course(); 1064 $coursecontext = context_course::instance($course->id); 1065 $assign = $this->create_instance($course); 1066 $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher'); 1067 $student = self::getDataGenerator()->create_and_enrol($course, 'student'); 1068 1069 // Remove the students capability to submit. 1070 $role = $DB->get_field('role', 'id', ['shortname' => 'student']); 1071 assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext); 1072 1073 $participant = $assign->get_participant($student->id); 1074 1075 self::assertNull($participant); 1076 } 1077 1078 /** 1079 * Tests that if a student that has submitted but can no longer submit is a participant. 1080 */ 1081 public function test_get_participant_with_submission_no_capability() { 1082 global $DB; 1083 $this->resetAfterTest(); 1084 $course = self::getDataGenerator()->create_course(); 1085 $coursecontext = context_course::instance($course->id); 1086 $assign = $this->create_instance($course); 1087 $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher'); 1088 $student = self::getDataGenerator()->create_and_enrol($course, 'student'); 1089 1090 // Simulate a submission. 1091 $this->add_submission($student, $assign); 1092 $this->submit_for_grading($student, $assign); 1093 1094 // Remove the students capability to submit. 1095 $role = $DB->get_field('role', 'id', ['shortname' => 'student']); 1096 assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext); 1097 1098 $participant = $assign->get_participant($student->id); 1099 1100 self::assertNotNull($participant); 1101 self::assertEquals($student->id, $participant->id); 1102 self::assertTrue($participant->submitted); 1103 self::assertTrue($participant->requiregrading); 1104 self::assertFalse($participant->grantedextension); 1105 } 1106 1107 public function test_get_participant_with_graded_submission() { 1108 $this->resetAfterTest(); 1109 $course = $this->getDataGenerator()->create_course(); 1110 $assign = $this->create_instance($course); 1111 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1112 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1113 1114 // Simulate a submission. 1115 $this->add_submission($student, $assign); 1116 $this->submit_for_grading($student, $assign); 1117 1118 $this->mark_submission($teacher, $assign, $student, 50.0); 1119 1120 $data = new stdClass(); 1121 $data->grade = '50.0'; 1122 $assign->testable_apply_grade_to_user($data, $student->id, 0); 1123 1124 $participant = $assign->get_participant($student->id); 1125 1126 $this->assertEquals($student->id, $participant->id); 1127 $this->assertTrue($participant->submitted); 1128 $this->assertFalse($participant->requiregrading); 1129 $this->assertFalse($participant->grantedextension); 1130 } 1131 1132 /** 1133 * No active group and non-group submissions disallowed => 2 groups. 1134 */ 1135 public function test_count_teams_no_active_non_group_allowed() { 1136 $this->resetAfterTest(); 1137 1138 $course = $this->getDataGenerator()->create_course(); 1139 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1140 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1141 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1142 1143 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 1144 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1145 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1146 groups_add_member($group1, $student1); 1147 groups_add_member($group2, $student2); 1148 1149 $this->setUser($teacher); 1150 $assign = $this->create_instance($course, ['teamsubmission' => 1]); 1151 1152 $this->assertEquals(2, $assign->count_teams()); 1153 } 1154 1155 /** 1156 * No active group and non group submissions allowed => 2 groups + the default one. 1157 */ 1158 public function test_count_teams_non_group_allowed() { 1159 $this->resetAfterTest(); 1160 1161 $course = $this->getDataGenerator()->create_course(); 1162 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1163 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1164 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1165 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1166 1167 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 1168 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1169 1170 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 1171 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1172 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id)); 1173 1174 groups_add_member($group1, $student1); 1175 groups_add_member($group2, $student2); 1176 1177 $assign = $this->create_instance($course, [ 1178 'teamsubmission' => 1, 1179 'teamsubmissiongroupingid' => $grouping->id, 1180 'preventsubmissionnotingroup' => false, 1181 ]); 1182 1183 $this->setUser($teacher); 1184 $this->assertEquals(3, $assign->count_teams()); 1185 1186 // Active group only. 1187 $this->assertEquals(1, $assign->count_teams($group1->id)); 1188 $this->assertEquals(1, $assign->count_teams($group2->id)); 1189 } 1190 1191 /** 1192 * Active group => just selected one. 1193 */ 1194 public function test_count_teams_no_active_group() { 1195 $this->resetAfterTest(); 1196 1197 $course = $this->getDataGenerator()->create_course(); 1198 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1199 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1200 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1201 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1202 1203 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 1204 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1205 1206 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 1207 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1208 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id)); 1209 1210 groups_add_member($group1, $student1); 1211 groups_add_member($group2, $student2); 1212 1213 $assign = $this->create_instance($course, [ 1214 'teamsubmission' => 1, 1215 'preventsubmissionnotingroup' => true, 1216 ]); 1217 1218 $this->setUser($teacher); 1219 $this->assertEquals(2, $assign->count_teams()); 1220 1221 // Active group only. 1222 $this->assertEquals(1, $assign->count_teams($group1->id)); 1223 $this->assertEquals(1, $assign->count_teams($group2->id)); 1224 } 1225 1226 /** 1227 * Active group => just selected one. 1228 */ 1229 public function test_count_teams_groups_only() { 1230 $this->resetAfterTest(); 1231 1232 $course = $this->getDataGenerator()->create_course(); 1233 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 1234 1235 $assign = $this->create_instance($course, [ 1236 'teamsubmission' => 1, 1237 'teamsubmissiongroupingid' => $grouping->id, 1238 'preventsubmissionnotingroup' => false, 1239 ]); 1240 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1241 1242 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1243 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1244 groups_add_member($group1, $student1); 1245 1246 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1247 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1248 groups_add_member($group2, $student2); 1249 1250 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 1251 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id)); 1252 1253 $this->setUser($teacher); 1254 1255 $assign = $this->create_instance($course, [ 1256 'teamsubmission' => 1, 1257 'preventsubmissionnotingroup' => true, 1258 ]); 1259 $this->assertEquals(2, $assign->count_teams()); 1260 } 1261 1262 public function test_submit_to_default_group() { 1263 global $DB, $SESSION; 1264 1265 $this->resetAfterTest(); 1266 1267 $course = $this->getDataGenerator()->create_course(); 1268 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1269 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1270 1271 $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]); 1272 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1273 1274 $assign = $this->create_instance($course, [ 1275 'teamsubmission' => 1, 1276 'assignsubmission_onlinetext_enabled' => 1, 1277 'submissiondrafts' => 0, 1278 'groupmode' => VISIBLEGROUPS, 1279 ]); 1280 1281 $usergroup = $assign->get_submission_group($student->id); 1282 $this->assertFalse($usergroup, 'New student is in default group'); 1283 1284 // Add a submission. 1285 $this->add_submission($student, $assign); 1286 $this->submit_for_grading($student, $assign); 1287 1288 // Set active groups to all groups. 1289 $this->setUser($teacher); 1290 $SESSION->activegroup[$course->id]['aag'][0] = 0; 1291 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1292 1293 // Set an active group. 1294 $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id; 1295 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1296 } 1297 1298 public function test_count_submissions_no_draft() { 1299 $this->resetAfterTest(); 1300 1301 $course = $this->getDataGenerator()->create_course(); 1302 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1303 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1304 1305 $assign = $this->create_instance($course, [ 1306 'assignsubmission_onlinetext_enabled' => 1, 1307 ]); 1308 1309 $assign->get_user_submission($student->id, true); 1310 1311 // Note: Drafts count as a submission. 1312 $this->assertEquals(0, $assign->count_grades()); 1313 $this->assertEquals(0, $assign->count_submissions()); 1314 $this->assertEquals(1, $assign->count_submissions(true)); 1315 $this->assertEquals(0, $assign->count_submissions_need_grading()); 1316 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW)); 1317 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1318 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1319 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED)); 1320 } 1321 1322 public function test_count_submissions_draft() { 1323 $this->resetAfterTest(); 1324 1325 $course = $this->getDataGenerator()->create_course(); 1326 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1327 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1328 1329 $assign = $this->create_instance($course, [ 1330 'assignsubmission_onlinetext_enabled' => 1, 1331 ]); 1332 1333 $this->add_submission($student, $assign); 1334 1335 // Note: Drafts count as a submission. 1336 $this->assertEquals(0, $assign->count_grades()); 1337 $this->assertEquals(1, $assign->count_submissions()); 1338 $this->assertEquals(1, $assign->count_submissions(true)); 1339 $this->assertEquals(0, $assign->count_submissions_need_grading()); 1340 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW)); 1341 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1342 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1343 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED)); 1344 } 1345 1346 public function test_count_submissions_submitted() { 1347 global $SESSION; 1348 1349 $this->resetAfterTest(); 1350 1351 $course = $this->getDataGenerator()->create_course(); 1352 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1353 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1354 1355 $assign = $this->create_instance($course, [ 1356 'assignsubmission_onlinetext_enabled' => 1, 1357 ]); 1358 1359 $this->add_submission($student, $assign); 1360 $this->submit_for_grading($student, $assign); 1361 1362 $this->assertEquals(0, $assign->count_grades()); 1363 $this->assertEquals(1, $assign->count_submissions()); 1364 $this->assertEquals(1, $assign->count_submissions(true)); 1365 $this->assertEquals(1, $assign->count_submissions_need_grading()); 1366 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW)); 1367 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1368 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1369 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED)); 1370 } 1371 1372 public function test_count_submissions_graded() { 1373 $this->resetAfterTest(); 1374 1375 $course = $this->getDataGenerator()->create_course(); 1376 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1377 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1378 1379 $assign = $this->create_instance($course, [ 1380 'assignsubmission_onlinetext_enabled' => 1, 1381 ]); 1382 1383 $this->add_submission($student, $assign); 1384 $this->submit_for_grading($student, $assign); 1385 $this->mark_submission($teacher, $assign, $student, 50.0); 1386 1387 // Although it has been graded, it is still marked as submitted. 1388 $this->assertEquals(1, $assign->count_grades()); 1389 $this->assertEquals(1, $assign->count_submissions()); 1390 $this->assertEquals(1, $assign->count_submissions(true)); 1391 $this->assertEquals(0, $assign->count_submissions_need_grading()); 1392 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW)); 1393 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1394 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1395 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED)); 1396 } 1397 1398 public function test_count_submissions_graded_group() { 1399 global $SESSION; 1400 1401 $this->resetAfterTest(); 1402 1403 $course = $this->getDataGenerator()->create_course(); 1404 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1405 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1406 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1407 $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1408 groups_add_member($group, $student); 1409 1410 $assign = $this->create_instance($course, [ 1411 'assignsubmission_onlinetext_enabled' => 1, 1412 'groupmode' => VISIBLEGROUPS, 1413 ]); 1414 1415 $this->add_submission($student, $assign); 1416 $this->submit_for_grading($student, $assign); 1417 1418 // The user should still be listed when fetching all groups. 1419 $this->setUser($teacher); 1420 $SESSION->activegroup[$course->id]['aag'][0] = 0; 1421 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1422 1423 // The user should still be listed when fetching just their group. 1424 $SESSION->activegroup[$course->id]['aag'][0] = $group->id; 1425 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1426 1427 // The user should still be listed when fetching just their group. 1428 $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id; 1429 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1430 } 1431 1432 // TODO 1433 public function x_test_count_submissions_for_team() { 1434 $this->resetAfterTest(); 1435 1436 $course = $this->getDataGenerator()->create_course(); 1437 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1438 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1439 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1440 $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1441 groups_add_member($group, $student); 1442 1443 $assign = $this->create_instance($course, [ 1444 'assignsubmission_onlinetext_enabled' => 1, 1445 'teamsubmission' => 1, 1446 ]); 1447 1448 // Add a graded submission. 1449 $this->add_submission($student, $assign); 1450 1451 1452 1453 // Simulate adding a grade. 1454 $this->setUser($teacher); 1455 $data = new stdClass(); 1456 $data->grade = '50.0'; 1457 $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0); 1458 1459 // Simulate a submission. 1460 $this->setUser($this->extrastudents[1]); 1461 $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true); 1462 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 1463 $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false); 1464 $data = new stdClass(); 1465 $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(), 1466 'text' => 'Submission text', 1467 'format' => FORMAT_MOODLE); 1468 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 1469 $plugin->save($submission, $data); 1470 1471 // Simulate a submission. 1472 $this->setUser($this->extrastudents[2]); 1473 $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true); 1474 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 1475 $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false); 1476 $data = new stdClass(); 1477 $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(), 1478 'text' => 'Submission text', 1479 'format' => FORMAT_MOODLE); 1480 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 1481 $plugin->save($submission, $data); 1482 1483 // Simulate a submission. 1484 $this->setUser($this->extrastudents[3]); 1485 $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true); 1486 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 1487 $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false); 1488 $data = new stdClass(); 1489 $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(), 1490 'text' => 'Submission text', 1491 'format' => FORMAT_MOODLE); 1492 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 1493 $plugin->save($submission, $data); 1494 1495 // Simulate adding a grade. 1496 $this->setUser($teacher); 1497 $data = new stdClass(); 1498 $data->grade = '50.0'; 1499 $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0); 1500 $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0); 1501 1502 // Create a new submission with status NEW. 1503 $this->setUser($this->extrastudents[4]); 1504 $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true); 1505 1506 $this->assertEquals(2, $assign->count_grades()); 1507 $this->assertEquals(4, $assign->count_submissions()); 1508 $this->assertEquals(5, $assign->count_submissions(true)); 1509 $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1510 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1511 } 1512 1513 public function test_get_grading_userid_list_only_active() { 1514 $this->resetAfterTest(); 1515 1516 $course = $this->getDataGenerator()->create_course(); 1517 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1518 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1519 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 1520 1521 $this->setUser($teacher); 1522 1523 $assign = $this->create_instance($course); 1524 $this->assertCount(1, $assign->testable_get_grading_userid_list()); 1525 } 1526 1527 public function test_get_grading_userid_list_all() { 1528 $this->resetAfterTest(); 1529 1530 $course = $this->getDataGenerator()->create_course(); 1531 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1532 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1533 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 1534 1535 $this->setUser($teacher); 1536 set_user_preference('grade_report_showonlyactiveenrol', false); 1537 1538 $assign = $this->create_instance($course); 1539 $this->assertCount(2, $assign->testable_get_grading_userid_list()); 1540 } 1541 1542 public function test_cron() { 1543 global $PAGE; 1544 $this->resetAfterTest(); 1545 1546 // First run cron so there are no messages waiting to be sent (from other tests). 1547 cron_setup_user(); 1548 assign::cron(); 1549 1550 $course = $this->getDataGenerator()->create_course(); 1551 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1552 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1553 1554 // Now create an assignment and add some feedback. 1555 $this->setUser($teacher); 1556 $assign = $this->create_instance($course, [ 1557 'sendstudentnotifications' => 1, 1558 ]); 1559 1560 $this->add_submission($student, $assign); 1561 $this->submit_for_grading($student, $assign); 1562 $this->mark_submission($teacher, $assign, $student, 50.0); 1563 1564 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1565 cron_setup_user(); 1566 $sink = $this->redirectMessages(); 1567 assign::cron(); 1568 $messages = $sink->get_messages(); 1569 1570 $this->assertEquals(1, count($messages)); 1571 $this->assertEquals(1, $messages[0]->notification); 1572 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname); 1573 // Test customdata. 1574 $customdata = json_decode($messages[0]->customdata); 1575 $this->assertEquals($assign->get_course_module()->id, $customdata->cmid); 1576 $this->assertEquals($assign->get_instance()->id, $customdata->instance); 1577 $this->assertEquals('feedbackavailable', $customdata->messagetype); 1578 $userpicture = new user_picture($teacher); 1579 $userpicture->size = 1; // Use f1 size. 1580 $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl); 1581 $this->assertEquals(0, $customdata->uniqueidforuser); // Not used in this case. 1582 $this->assertFalse($customdata->blindmarking); 1583 } 1584 1585 public function test_cron_without_notifications() { 1586 $this->resetAfterTest(); 1587 1588 // First run cron so there are no messages waiting to be sent (from other tests). 1589 cron_setup_user(); 1590 assign::cron(); 1591 1592 $course = $this->getDataGenerator()->create_course(); 1593 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1594 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1595 1596 // Now create an assignment and add some feedback. 1597 $this->setUser($teacher); 1598 $assign = $this->create_instance($course, [ 1599 'sendstudentnotifications' => 1, 1600 ]); 1601 1602 $this->add_submission($student, $assign); 1603 $this->submit_for_grading($student, $assign); 1604 $this->mark_submission($teacher, $assign, $student, 50.0, [ 1605 'sendstudentnotifications' => 0, 1606 ]); 1607 1608 cron_setup_user(); 1609 $sink = $this->redirectMessages(); 1610 assign::cron(); 1611 $messages = $sink->get_messages(); 1612 1613 $this->assertEquals(0, count($messages)); 1614 } 1615 1616 public function test_cron_regraded() { 1617 $this->resetAfterTest(); 1618 1619 // First run cron so there are no messages waiting to be sent (from other tests). 1620 cron_setup_user(); 1621 assign::cron(); 1622 1623 $course = $this->getDataGenerator()->create_course(); 1624 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1625 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1626 1627 // Now create an assignment and add some feedback. 1628 $this->setUser($teacher); 1629 $assign = $this->create_instance($course, [ 1630 'sendstudentnotifications' => 1, 1631 ]); 1632 1633 $this->add_submission($student, $assign); 1634 $this->submit_for_grading($student, $assign); 1635 $this->mark_submission($teacher, $assign, $student, 50.0); 1636 1637 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1638 cron_setup_user(); 1639 assign::cron(); 1640 1641 // Regrade. 1642 $this->mark_submission($teacher, $assign, $student, 50.0); 1643 1644 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1645 cron_setup_user(); 1646 $sink = $this->redirectMessages(); 1647 assign::cron(); 1648 $messages = $sink->get_messages(); 1649 1650 $this->assertEquals(1, count($messages)); 1651 $this->assertEquals(1, $messages[0]->notification); 1652 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname); 1653 } 1654 1655 /** 1656 * Test delivery of grade notifications as controlled by marking workflow. 1657 */ 1658 public function test_markingworkflow_cron() { 1659 $this->resetAfterTest(); 1660 1661 // First run cron so there are no messages waiting to be sent (from other tests). 1662 cron_setup_user(); 1663 assign::cron(); 1664 1665 $course = $this->getDataGenerator()->create_course(); 1666 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1667 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1668 1669 // Now create an assignment and add some feedback. 1670 $this->setUser($teacher); 1671 $assign = $this->create_instance($course, [ 1672 'sendstudentnotifications' => 1, 1673 'markingworkflow' => 1, 1674 ]); 1675 1676 // Mark a submission but set the workflowstate to an unreleased state. 1677 // This should not trigger a notification. 1678 $this->add_submission($student, $assign); 1679 $this->submit_for_grading($student, $assign); 1680 $this->mark_submission($teacher, $assign, $student, 50.0, [ 1681 'sendstudentnotifications' => 1, 1682 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE, 1683 ]); 1684 1685 cron_setup_user(); 1686 $sink = $this->redirectMessages(); 1687 assign::cron(); 1688 $messages = $sink->get_messages(); 1689 1690 $this->assertEquals(0, count($messages)); 1691 1692 // Transition to the released state. 1693 $this->setUser($teacher); 1694 $submission = $assign->get_user_submission($student->id, true); 1695 $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED; 1696 $assign->testable_apply_grade_to_user($submission, $student->id, 0); 1697 1698 // Now run cron and see that one message was sent. 1699 cron_setup_user(); 1700 $sink = $this->redirectMessages(); 1701 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1702 assign::cron(); 1703 $messages = $sink->get_messages(); 1704 1705 $this->assertEquals(1, count($messages)); 1706 $this->assertEquals(1, $messages[0]->notification); 1707 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname); 1708 } 1709 1710 public function test_cron_message_includes_courseid() { 1711 $this->resetAfterTest(); 1712 1713 // First run cron so there are no messages waiting to be sent (from other tests). 1714 cron_setup_user(); 1715 assign::cron(); 1716 1717 $course = $this->getDataGenerator()->create_course(); 1718 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1719 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1720 1721 // Now create an assignment and add some feedback. 1722 $this->setUser($teacher); 1723 $assign = $this->create_instance($course, [ 1724 'sendstudentnotifications' => 1, 1725 ]); 1726 1727 // Mark a submission but set the workflowstate to an unreleased state. 1728 // This should not trigger a notification. 1729 $this->add_submission($student, $assign); 1730 $this->submit_for_grading($student, $assign); 1731 $this->mark_submission($teacher, $assign, $student); 1732 phpunit_util::stop_message_redirection(); 1733 1734 // Now run cron and see that one message was sent. 1735 cron_setup_user(); 1736 $this->preventResetByRollback(); 1737 $sink = $this->redirectEvents(); 1738 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1739 assign::cron(); 1740 1741 $events = $sink->get_events(); 1742 $event = reset($events); 1743 $this->assertInstanceOf('\core\event\notification_sent', $event); 1744 $this->assertEquals($assign->get_course()->id, $event->other['courseid']); 1745 $sink->close(); 1746 } 1747 1748 public function test_is_graded() { 1749 $this->resetAfterTest(); 1750 1751 $course = $this->getDataGenerator()->create_course(); 1752 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1753 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1754 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1755 1756 $assign = $this->create_instance($course); 1757 1758 $this->add_submission($student, $assign); 1759 $this->submit_for_grading($student, $assign); 1760 $this->mark_submission($teacher, $assign, $student, 50.0); 1761 1762 $this->setUser($teacher); 1763 $this->assertEquals(true, $assign->testable_is_graded($student->id)); 1764 $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id)); 1765 } 1766 1767 public function test_can_grade() { 1768 global $DB; 1769 1770 $this->resetAfterTest(); 1771 1772 $course = $this->getDataGenerator()->create_course(); 1773 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1774 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1775 1776 $assign = $this->create_instance($course); 1777 1778 $this->setUser($student); 1779 $this->assertEquals(false, $assign->can_grade()); 1780 1781 $this->setUser($teacher); 1782 $this->assertEquals(true, $assign->can_grade()); 1783 1784 // Test the viewgrades capability for other users. 1785 $this->setUser(); 1786 $this->assertTrue($assign->can_grade($teacher->id)); 1787 $this->assertFalse($assign->can_grade($student->id)); 1788 1789 // Test the viewgrades capability - without mod/assign:grade. 1790 $this->setUser($student); 1791 1792 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 1793 assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id); 1794 $this->assertEquals(false, $assign->can_grade()); 1795 } 1796 1797 public function test_can_view_submission() { 1798 global $DB; 1799 1800 $this->resetAfterTest(); 1801 1802 $course = $this->getDataGenerator()->create_course(); 1803 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1804 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1805 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1806 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1807 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 1808 1809 $assign = $this->create_instance($course); 1810 1811 $this->setUser($student); 1812 $this->assertEquals(true, $assign->can_view_submission($student->id)); 1813 $this->assertEquals(false, $assign->can_view_submission($otherstudent->id)); 1814 $this->assertEquals(false, $assign->can_view_submission($teacher->id)); 1815 1816 $this->setUser($teacher); 1817 $this->assertEquals(true, $assign->can_view_submission($student->id)); 1818 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id)); 1819 $this->assertEquals(true, $assign->can_view_submission($teacher->id)); 1820 $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id)); 1821 1822 $this->setUser($editingteacher); 1823 $this->assertEquals(true, $assign->can_view_submission($student->id)); 1824 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id)); 1825 $this->assertEquals(true, $assign->can_view_submission($teacher->id)); 1826 $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id)); 1827 1828 // Test the viewgrades capability - without mod/assign:grade. 1829 $this->setUser($student); 1830 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 1831 assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id); 1832 $this->assertEquals(true, $assign->can_view_submission($student->id)); 1833 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id)); 1834 $this->assertEquals(true, $assign->can_view_submission($teacher->id)); 1835 $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id)); 1836 } 1837 1838 public function test_update_submission() { 1839 $this->resetAfterTest(); 1840 1841 $course = $this->getDataGenerator()->create_course(); 1842 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1843 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1844 1845 $assign = $this->create_instance($course); 1846 1847 $this->add_submission($student, $assign); 1848 $submission = $assign->get_user_submission($student->id, 0); 1849 $assign->testable_update_submission($submission, $student->id, true, true); 1850 1851 $this->setUser($teacher); 1852 1853 // Verify the gradebook update. 1854 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1855 1856 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1857 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified); 1858 } 1859 1860 public function test_update_submission_team() { 1861 $this->resetAfterTest(); 1862 1863 $course = $this->getDataGenerator()->create_course(); 1864 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1865 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1866 1867 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1868 groups_add_member($group, $student); 1869 1870 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1871 groups_add_member($group, $otherstudent); 1872 1873 $assign = $this->create_instance($course, [ 1874 'teamsubmission' => 1, 1875 ]); 1876 1877 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1878 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1879 $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified); 1880 1881 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id); 1882 $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id])); 1883 $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified); 1884 1885 $this->add_submission($student, $assign); 1886 $submission = $assign->get_group_submission($student->id, 0, true); 1887 $assign->testable_update_submission($submission, $student->id, true, true); 1888 1889 // Verify the gradebook update for the student. 1890 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1891 1892 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1893 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified); 1894 1895 // Verify the gradebook update for the other student. 1896 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id); 1897 1898 $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id])); 1899 $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified); 1900 } 1901 1902 public function test_update_submission_suspended() { 1903 $this->resetAfterTest(); 1904 1905 $course = $this->getDataGenerator()->create_course(); 1906 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1907 $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 1908 1909 $assign = $this->create_instance($course); 1910 1911 $this->add_submission($student, $assign); 1912 $submission = $assign->get_user_submission($student->id, 0); 1913 $assign->testable_update_submission($submission, $student->id, true, false); 1914 1915 $this->setUser($teacher); 1916 1917 // Verify the gradebook update. 1918 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1919 1920 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1921 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified); 1922 } 1923 1924 public function test_update_submission_blind() { 1925 $this->resetAfterTest(); 1926 1927 $course = $this->getDataGenerator()->create_course(); 1928 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1929 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1930 1931 $assign = $this->create_instance($course, [ 1932 'blindmarking' => 1, 1933 ]); 1934 1935 $this->add_submission($student, $assign); 1936 $submission = $assign->get_user_submission($student->id, 0); 1937 $assign->testable_update_submission($submission, $student->id, true, false); 1938 1939 // Verify the gradebook update. 1940 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1941 1942 // The usermodified is not set because this is blind marked. 1943 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1944 $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified); 1945 } 1946 1947 public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() { 1948 global $PAGE; 1949 1950 $this->resetAfterTest(); 1951 1952 $course = $this->getDataGenerator()->create_course(); 1953 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1954 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1955 1956 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1957 groups_add_member($group, $student); 1958 1959 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1960 groups_add_member($group, $otherstudent); 1961 1962 $assign = $this->create_instance($course, [ 1963 'teamsubmission' => 1, 1964 'assignsubmission_onlinetext_enabled' => 1, 1965 'submissiondrafts' => 1, 1966 'requireallteammemberssubmit' => 1, 1967 ]); 1968 1969 // Now verify group assignments. 1970 $this->setUser($teacher); 1971 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 1972 1973 // Add a submission. 1974 $this->add_submission($student, $assign); 1975 1976 // Check we can see the submit button. 1977 $this->setUser($student); 1978 $output = $assign->view_student_summary($student, true); 1979 $this->assertContains(get_string('submitassignment', 'assign'), $output); 1980 1981 $submission = $assign->get_group_submission($student->id, 0, true); 1982 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 1983 $assign->testable_update_submission($submission, $student->id, true, true); 1984 1985 // Check that the student does not see "Submit" button. 1986 $output = $assign->view_student_summary($student, true); 1987 $this->assertNotContains(get_string('submitassignment', 'assign'), $output); 1988 1989 // Change to another user in the same group. 1990 $this->setUser($otherstudent); 1991 $output = $assign->view_student_summary($otherstudent, true); 1992 $this->assertContains(get_string('submitassignment', 'assign'), $output); 1993 1994 $submission = $assign->get_group_submission($otherstudent->id, 0, true); 1995 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 1996 $assign->testable_update_submission($submission, $otherstudent->id, true, true); 1997 $output = $assign->view_student_summary($otherstudent, true); 1998 $this->assertNotContains(get_string('submitassignment', 'assign'), $output); 1999 } 2000 2001 public function test_group_submissions_submit_for_marking() { 2002 global $PAGE; 2003 2004 $this->resetAfterTest(); 2005 2006 $course = $this->getDataGenerator()->create_course(); 2007 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2008 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2009 2010 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2011 groups_add_member($group, $student); 2012 2013 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2014 groups_add_member($group, $otherstudent); 2015 2016 // Now verify group assignments. 2017 $this->setUser($teacher); 2018 $time = time(); 2019 $assign = $this->create_instance($course, [ 2020 'teamsubmission' => 1, 2021 'assignsubmission_onlinetext_enabled' => 1, 2022 'submissiondrafts' => 1, 2023 'requireallteammemberssubmit' => 0, 2024 'duedate' => $time - (2 * DAYSECS), 2025 ]); 2026 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2027 2028 // Add a submission. 2029 $this->add_submission($student, $assign); 2030 2031 2032 // Check we can see the submit button. 2033 $output = $assign->view_student_summary($student, true); 2034 $this->assertContains(get_string('submitassignment', 'assign'), $output); 2035 $this->assertContains(get_string('timeremaining', 'assign'), $output); 2036 $difftime = time() - $time; 2037 $this->assertContains(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output); 2038 2039 $submission = $assign->get_group_submission($student->id, 0, true); 2040 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 2041 $assign->testable_update_submission($submission, $student->id, true, true); 2042 2043 // Check that the student does not see "Submit" button. 2044 $output = $assign->view_student_summary($student, true); 2045 $this->assertNotContains(get_string('submitassignment', 'assign'), $output); 2046 2047 // Change to another user in the same group. 2048 $this->setUser($otherstudent); 2049 $output = $assign->view_student_summary($otherstudent, true); 2050 $this->assertNotContains(get_string('submitassignment', 'assign'), $output); 2051 2052 // Check that time remaining is not overdue. 2053 $this->assertContains(get_string('timeremaining', 'assign'), $output); 2054 $difftime = time() - $time; 2055 $this->assertContains(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output); 2056 2057 $submission = $assign->get_group_submission($otherstudent->id, 0, true); 2058 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 2059 $assign->testable_update_submission($submission, $otherstudent->id, true, true); 2060 $output = $assign->view_student_summary($otherstudent, true); 2061 $this->assertNotContains(get_string('submitassignment', 'assign'), $output); 2062 } 2063 2064 public function test_submissions_open() { 2065 global $DB; 2066 2067 $this->resetAfterTest(); 2068 2069 $course = $this->getDataGenerator()->create_course(); 2070 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2071 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2072 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2073 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2074 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 2075 2076 $this->setAdminUser(); 2077 2078 $now = time(); 2079 $tomorrow = $now + DAYSECS; 2080 $oneweek = $now + WEEKSECS; 2081 $yesterday = $now - DAYSECS; 2082 2083 $assign = $this->create_instance($course); 2084 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2085 2086 $assign = $this->create_instance($course, ['duedate' => $tomorrow]); 2087 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2088 2089 $assign = $this->create_instance($course, ['duedate' => $yesterday]); 2090 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2091 2092 $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]); 2093 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2094 2095 $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]); 2096 $this->assertEquals(false, $assign->testable_submissions_open($student->id)); 2097 2098 $assign->testable_save_user_extension($student->id, $tomorrow); 2099 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2100 2101 $assign = $this->create_instance($course, ['submissiondrafts' => 1]); 2102 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2103 2104 $this->setUser($student); 2105 $submission = $assign->get_user_submission($student->id, true); 2106 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 2107 $assign->testable_update_submission($submission, $student->id, true, false); 2108 2109 $this->setUser($teacher); 2110 $this->assertEquals(false, $assign->testable_submissions_open($student->id)); 2111 } 2112 2113 public function test_get_graders() { 2114 global $DB; 2115 2116 $this->resetAfterTest(); 2117 2118 $course = $this->getDataGenerator()->create_course(); 2119 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2120 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2121 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2122 2123 $this->setAdminUser(); 2124 2125 // Create an assignment with no groups. 2126 $assign = $this->create_instance($course); 2127 $this->assertCount(2, $assign->testable_get_graders($student->id)); 2128 } 2129 2130 public function test_get_graders_separate_groups() { 2131 global $DB; 2132 2133 $this->resetAfterTest(); 2134 2135 $course = $this->getDataGenerator()->create_course(); 2136 $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2137 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2138 $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2139 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2140 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2141 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2142 2143 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2144 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2145 groups_add_member($group1, $student); 2146 2147 $this->setAdminUser(); 2148 2149 // Force create an assignment with SEPARATEGROUPS. 2150 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2151 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2152 2153 $assign = $this->create_instance($course, [ 2154 'groupingid' => $grouping->id, 2155 'groupmode' => SEPARATEGROUPS, 2156 ]); 2157 2158 $this->assertCount(4, $assign->testable_get_graders($student->id)); 2159 2160 // Note the second student is in a group that is not in the grouping. 2161 // This means that we get all graders that are not in a group in the grouping. 2162 $this->assertCount(4, $assign->testable_get_graders($otherstudent->id)); 2163 } 2164 2165 public function test_get_notified_users() { 2166 global $CFG, $DB; 2167 2168 $this->resetAfterTest(); 2169 2170 $course = $this->getDataGenerator()->create_course(); 2171 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2172 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2173 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 2174 2175 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2176 groups_add_member($group1, $teacher); 2177 2178 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2179 groups_add_member($group1, $editingteacher); 2180 2181 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2182 groups_add_member($group1, $student); 2183 2184 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2185 $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2186 2187 $capability = 'mod/assign:receivegradernotifications'; 2188 $coursecontext = context_course::instance($course->id); 2189 $role = $DB->get_record('role', array('shortname' => 'teacher')); 2190 2191 $this->setUser($teacher); 2192 2193 // Create an assignment with no groups. 2194 $assign = $this->create_instance($course); 2195 2196 $this->assertCount(3, $assign->testable_get_notifiable_users($student->id)); 2197 2198 // Change nonediting teachers role to not receive grader notifications. 2199 assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext); 2200 2201 // Only the editing teachers will be returned. 2202 $this->assertCount(1, $assign->testable_get_notifiable_users($student->id)); 2203 2204 // Note the second student is in a group that is not in the grouping. 2205 // This means that we get all graders that are not in a group in the grouping. 2206 $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id)); 2207 } 2208 2209 public function test_get_notified_users_in_grouping() { 2210 global $CFG, $DB; 2211 2212 $this->resetAfterTest(); 2213 2214 $course = $this->getDataGenerator()->create_course(); 2215 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2216 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2217 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 2218 2219 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2220 groups_add_member($group1, $teacher); 2221 2222 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2223 groups_add_member($group1, $editingteacher); 2224 2225 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2226 groups_add_member($group1, $student); 2227 2228 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2229 $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2230 2231 // Force create an assignment with SEPARATEGROUPS. 2232 $assign = $this->create_instance($course, [ 2233 'groupingid' => $grouping->id, 2234 'groupmode' => SEPARATEGROUPS, 2235 ]); 2236 2237 // Student is in a group - only the tacher and editing teacher in the group shoudl be present. 2238 $this->setUser($student); 2239 $this->assertCount(2, $assign->testable_get_notifiable_users($student->id)); 2240 2241 // Note the second student is in a group that is not in the grouping. 2242 // This means that we get all graders that are not in a group in the grouping. 2243 $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id)); 2244 2245 // Change nonediting teachers role to not receive grader notifications. 2246 $capability = 'mod/assign:receivegradernotifications'; 2247 $coursecontext = context_course::instance($course->id); 2248 $role = $DB->get_record('role', ['shortname' => 'teacher']); 2249 assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext); 2250 2251 // Only the editing teachers will be returned. 2252 $this->assertCount(1, $assign->testable_get_notifiable_users($student->id)); 2253 2254 // Note the second student is in a group that is not in the grouping. 2255 // This means that we get all graders that are not in a group in the grouping. 2256 // Unfortunately there are no editing teachers who are not in a group. 2257 $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id)); 2258 } 2259 2260 public function test_group_members_only() { 2261 global $CFG; 2262 2263 $this->resetAfterTest(); 2264 2265 $course = $this->getDataGenerator()->create_course(); 2266 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2267 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2268 $this->getDataGenerator()->create_grouping_group([ 2269 'groupid' => $group1->id, 2270 'groupingid' => $grouping->id, 2271 ]); 2272 2273 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2274 $this->getDataGenerator()->create_grouping_group([ 2275 'groupid' => $group2->id, 2276 'groupingid' => $grouping->id, 2277 ]); 2278 2279 $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2280 2281 // Add users in the following groups 2282 // - Teacher - Group 1. 2283 // - Student - Group 1. 2284 // - Student - Group 2. 2285 // - Student - Unrelated Group 2286 // - Student - No group. 2287 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2288 groups_add_member($group1, $teacher); 2289 2290 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2291 groups_add_member($group1, $student); 2292 2293 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2294 groups_add_member($group2, $otherstudent); 2295 2296 $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2297 groups_add_member($group2, $otherstudent); 2298 2299 $this->getDataGenerator()->create_and_enrol($course, 'student'); 2300 2301 $this->setAdminUser(); 2302 2303 $CFG->enableavailability = true; 2304 $assign = $this->create_instance($course, [], [ 2305 'availability' => json_encode( 2306 \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()]) 2307 ), 2308 'groupingid' => $grouping->id, 2309 ]); 2310 2311 // The two students in groups should be returned, but not the teacher in the group, or the student not in the 2312 // group, or the student in an unrelated group. 2313 $this->setUser($teacher); 2314 $participants = $assign->list_participants(0, true); 2315 $this->assertCount(2, $participants); 2316 $this->assertTrue(isset($participants[$student->id])); 2317 $this->assertTrue(isset($participants[$otherstudent->id])); 2318 } 2319 2320 public function test_get_uniqueid_for_user() { 2321 $this->resetAfterTest(); 2322 2323 $course = $this->getDataGenerator()->create_course(); 2324 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2325 $students = []; 2326 for ($i = 0; $i < 10; $i++) { 2327 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2328 $students[$student->id] = $student; 2329 } 2330 2331 $this->setUser($teacher); 2332 $assign = $this->create_instance($course); 2333 2334 foreach ($students as $student) { 2335 $uniqueid = $assign->get_uniqueid_for_user($student->id); 2336 $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid)); 2337 } 2338 } 2339 2340 public function test_show_student_summary() { 2341 global $CFG, $PAGE; 2342 2343 $this->resetAfterTest(); 2344 2345 $course = $this->getDataGenerator()->create_course(); 2346 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2347 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2348 $this->setUser($teacher); 2349 $assign = $this->create_instance($course); 2350 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2351 2352 // No feedback should be available because this student has not been graded. 2353 $this->setUser($student); 2354 $output = $assign->view_student_summary($student, true); 2355 $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if there is no grade'); 2356 2357 // Simulate adding a grade. 2358 $this->add_submission($student, $assign); 2359 $this->submit_for_grading($student, $assign); 2360 $this->mark_submission($teacher, $assign, $student); 2361 2362 // Now we should see the feedback. 2363 $this->setUser($student); 2364 $output = $assign->view_student_summary($student, true); 2365 $this->assertRegexp('/Feedback/', $output, 'Show feedback if there is a grade'); 2366 2367 // Now hide the grade in gradebook. 2368 $this->setUser($teacher); 2369 require_once($CFG->libdir.'/gradelib.php'); 2370 $gradeitem = new grade_item(array( 2371 'itemtype' => 'mod', 2372 'itemmodule' => 'assign', 2373 'iteminstance' => $assign->get_instance()->id, 2374 'courseid' => $course->id)); 2375 2376 $gradeitem->set_hidden(1, false); 2377 2378 // No feedback should be available because the grade is hidden. 2379 $this->setUser($student); 2380 $output = $assign->view_student_summary($student, true); 2381 $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook'); 2382 2383 // Freeze the context. 2384 $this->setAdminUser(); 2385 $context = $assign->get_context(); 2386 $CFG->contextlocking = true; 2387 $context->set_locked(true); 2388 2389 // No feedback should be available because the grade is hidden. 2390 $this->setUser($student); 2391 $output = $assign->view_student_summary($student, true); 2392 $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook'); 2393 2394 // Show the feedback again - it should still be visible even in a frozen context. 2395 $this->setUser($teacher); 2396 $gradeitem->set_hidden(0, false); 2397 2398 $this->setUser($student); 2399 $output = $assign->view_student_summary($student, true); 2400 $this->assertRegexp('/Feedback/', $output, 'Show feedback if there is a grade'); 2401 } 2402 2403 public function test_show_student_summary_with_feedback() { 2404 global $CFG, $PAGE; 2405 2406 $this->resetAfterTest(); 2407 2408 $course = $this->getDataGenerator()->create_course(); 2409 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2410 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2411 $this->setUser($teacher); 2412 $assign = $this->create_instance($course, [ 2413 'assignfeedback_comments_enabled' => 1 2414 ]); 2415 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2416 2417 // No feedback should be available because this student has not been graded. 2418 $this->setUser($student); 2419 $output = $assign->view_student_summary($student, true); 2420 $this->assertNotRegexp('/Feedback/', $output); 2421 2422 // Simulate adding a grade. 2423 $this->add_submission($student, $assign); 2424 $this->submit_for_grading($student, $assign); 2425 $this->mark_submission($teacher, $assign, $student, null, [ 2426 'assignfeedbackcomments_editor' => [ 2427 'text' => 'Tomato sauce', 2428 'format' => FORMAT_MOODLE, 2429 ], 2430 ]); 2431 2432 // Should have feedback but no grade. 2433 $this->setUser($student); 2434 $output = $assign->view_student_summary($student, true); 2435 $this->assertRegexp('/Feedback/', $output); 2436 $this->assertRegexp('/Tomato sauce/', $output); 2437 $this->assertNotRegexp('/Grade/', $output, 'Do not show grade when there is no grade.'); 2438 $this->assertNotRegexp('/Graded on/', $output, 'Do not show graded date when there is no grade.'); 2439 2440 // Add a grade now. 2441 $this->mark_submission($teacher, $assign, $student, 50.0, [ 2442 'assignfeedbackcomments_editor' => [ 2443 'text' => 'Bechamel sauce', 2444 'format' => FORMAT_MOODLE, 2445 ], 2446 ]); 2447 2448 // Should have feedback but no grade. 2449 $this->setUser($student); 2450 $output = $assign->view_student_summary($student, true); 2451 $this->assertNotRegexp('/Tomato sauce/', $output); 2452 $this->assertRegexp('/Bechamel sauce/', $output); 2453 $this->assertRegexp('/Grade/', $output); 2454 $this->assertRegexp('/Graded on/', $output); 2455 2456 // Now hide the grade in gradebook. 2457 $this->setUser($teacher); 2458 $gradeitem = new grade_item(array( 2459 'itemtype' => 'mod', 2460 'itemmodule' => 'assign', 2461 'iteminstance' => $assign->get_instance()->id, 2462 'courseid' => $course->id)); 2463 2464 $gradeitem->set_hidden(1, false); 2465 2466 // No feedback should be available because the grade is hidden. 2467 $this->setUser($student); 2468 $output = $assign->view_student_summary($student, true); 2469 $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook'); 2470 } 2471 2472 /** 2473 * Test reopen behavior when in "Manual" mode. 2474 */ 2475 public function test_attempt_reopen_method_manual() { 2476 global $PAGE; 2477 2478 $this->resetAfterTest(); 2479 $course = $this->getDataGenerator()->create_course(); 2480 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2481 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2482 2483 $assign = $this->create_instance($course, [ 2484 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL, 2485 'maxattempts' => 3, 2486 'submissiondrafts' => 1, 2487 'assignsubmission_onlinetext_enabled' => 1, 2488 ]); 2489 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2490 2491 // Student should be able to see an add submission button. 2492 $this->setUser($student); 2493 $output = $assign->view_student_summary($student, true); 2494 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign'))); 2495 2496 // Add a submission. 2497 $this->add_submission($student, $assign); 2498 $this->submit_for_grading($student, $assign); 2499 2500 // Verify the student cannot make changes to the submission. 2501 $output = $assign->view_student_summary($student, true); 2502 $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign'))); 2503 2504 // Mark the submission. 2505 $this->mark_submission($teacher, $assign, $student); 2506 2507 // Check the student can see the grade. 2508 $this->setUser($student); 2509 $output = $assign->view_student_summary($student, true); 2510 $this->assertNotEquals(false, strpos($output, '50.0')); 2511 2512 // Allow the student another attempt. 2513 $teacher->ignoresesskey = true; 2514 $this->setUser($teacher); 2515 $result = $assign->testable_process_add_attempt($student->id); 2516 $this->assertEquals(true, $result); 2517 2518 // Check that the previous attempt is now in the submission history table. 2519 $this->setUser($student); 2520 $output = $assign->view_student_summary($student, true); 2521 // Need a better check. 2522 $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text'); 2523 2524 // Check that the student now has a button for Add a new attempt". 2525 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign'))); 2526 // Check that the student now does not have a button for Submit. 2527 $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign'))); 2528 2529 // Check that the student now has a submission history. 2530 $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign'))); 2531 2532 $this->setUser($teacher); 2533 // Check that the grading table loads correctly and contains this user. 2534 // This is also testing that we do not get duplicate rows in the grading table. 2535 $gradingtable = new assign_grading_table($assign, 100, '', 0, true); 2536 $output = $assign->get_renderer()->render($gradingtable); 2537 $this->assertEquals(true, strpos($output, $student->lastname)); 2538 2539 // Should be 1 not 2. 2540 $this->assertEquals(1, $assign->count_submissions()); 2541 $this->assertEquals(1, $assign->count_submissions_with_status('reopened')); 2542 $this->assertEquals(0, $assign->count_submissions_need_grading()); 2543 $this->assertEquals(1, $assign->count_grades()); 2544 2545 // Change max attempts to unlimited. 2546 $formdata = clone($assign->get_instance()); 2547 $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS; 2548 $formdata->instance = $formdata->id; 2549 $assign->update_instance($formdata); 2550 2551 // Mark the submission again. 2552 $this->mark_submission($teacher, $assign, $student, 60.0, [], 1); 2553 2554 // Check the grade exists. 2555 $this->setUser($teacher); 2556 $grades = $assign->get_user_grades_for_gradebook($student->id); 2557 $this->assertEquals(60, (int) $grades[$student->id]->rawgrade); 2558 2559 // Check we can reopen still. 2560 $result = $assign->testable_process_add_attempt($student->id); 2561 $this->assertEquals(true, $result); 2562 2563 // Should no longer have a grade because there is no grade for the latest attempt. 2564 $grades = $assign->get_user_grades_for_gradebook($student->id); 2565 $this->assertEmpty($grades); 2566 } 2567 2568 /** 2569 * Test reopen behavior when in "Reopen until pass" mode. 2570 */ 2571 public function test_attempt_reopen_method_untilpass() { 2572 global $PAGE; 2573 2574 $this->resetAfterTest(); 2575 $course = $this->getDataGenerator()->create_course(); 2576 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2577 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2578 2579 $assign = $this->create_instance($course, [ 2580 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS, 2581 'maxattempts' => 3, 2582 'submissiondrafts' => 1, 2583 'assignsubmission_onlinetext_enabled' => 1, 2584 ]); 2585 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2586 2587 // Set grade to pass to 80. 2588 $gradeitem = $assign->get_grade_item(); 2589 $gradeitem->gradepass = '80.0'; 2590 $gradeitem->update(); 2591 2592 // Student should be able to see an add submission button. 2593 $this->setUser($student); 2594 $output = $assign->view_student_summary($student, true); 2595 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign'))); 2596 2597 // Add a submission. 2598 $this->add_submission($student, $assign); 2599 $this->submit_for_grading($student, $assign); 2600 2601 // Verify the student cannot make a new attempt. 2602 $output = $assign->view_student_summary($student, true); 2603 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign'))); 2604 2605 // Mark the submission as non-passing. 2606 $this->mark_submission($teacher, $assign, $student, 50.0); 2607 2608 // Check the student can see the grade. 2609 $this->setUser($student); 2610 $output = $assign->view_student_summary($student, true); 2611 $this->assertNotEquals(false, strpos($output, '50.0')); 2612 2613 // Check that the student now has a button for Add a new attempt. 2614 $output = $assign->view_student_summary($student, true); 2615 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign'))); 2616 2617 // Check that the student now does not have a button for Submit. 2618 $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign'))); 2619 2620 // Check that the student now has a submission history. 2621 $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign'))); 2622 2623 // Add a second submission. 2624 $this->add_submission($student, $assign); 2625 $this->submit_for_grading($student, $assign); 2626 2627 // Mark the submission as passing. 2628 $this->mark_submission($teacher, $assign, $student, 80.0); 2629 2630 // Check that the student does not have a button for Add a new attempt. 2631 $this->setUser($student); 2632 $output = $assign->view_student_summary($student, true); 2633 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign'))); 2634 2635 // Re-mark the submission as not passing. 2636 $this->mark_submission($teacher, $assign, $student, 40.0, [], 1); 2637 2638 // Check that the student now has a button for Add a new attempt. 2639 $this->setUser($student); 2640 $output = $assign->view_student_summary($student, true); 2641 $this->assertRegexp('/' . get_string('addnewattempt', 'assign') . '/', $output); 2642 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign'))); 2643 } 2644 2645 public function test_attempt_reopen_method_untilpass_passing() { 2646 global $PAGE; 2647 2648 $this->resetAfterTest(); 2649 $course = $this->getDataGenerator()->create_course(); 2650 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2651 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2652 2653 $assign = $this->create_instance($course, [ 2654 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS, 2655 'maxattempts' => 3, 2656 'submissiondrafts' => 1, 2657 'assignsubmission_onlinetext_enabled' => 1, 2658 ]); 2659 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2660 2661 // Set grade to pass to 80. 2662 $gradeitem = $assign->get_grade_item(); 2663 $gradeitem->gradepass = '80.0'; 2664 $gradeitem->update(); 2665 2666 // Student should be able to see an add submission button. 2667 $this->setUser($student); 2668 $output = $assign->view_student_summary($student, true); 2669 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign'))); 2670 2671 // Add a submission as a student. 2672 $this->add_submission($student, $assign); 2673 $this->submit_for_grading($student, $assign); 2674 2675 // Mark the submission as passing. 2676 $this->mark_submission($teacher, $assign, $student, 100.0); 2677 2678 // Check the student can see the grade. 2679 $this->setUser($student); 2680 $output = $assign->view_student_summary($student, true); 2681 $this->assertNotEquals(false, strpos($output, '100.0')); 2682 2683 // Check that the student does not have a button for Add a new attempt. 2684 $output = $assign->view_student_summary($student, true); 2685 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign'))); 2686 } 2687 2688 public function test_attempt_reopen_method_untilpass_no_passing_requirement() { 2689 global $PAGE; 2690 2691 $this->resetAfterTest(); 2692 $course = $this->getDataGenerator()->create_course(); 2693 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2694 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2695 2696 $assign = $this->create_instance($course, [ 2697 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS, 2698 'maxattempts' => 3, 2699 'submissiondrafts' => 1, 2700 'assignsubmission_onlinetext_enabled' => 1, 2701 ]); 2702 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2703 2704 // Set grade to pass to 0, so that no attempts should reopen. 2705 $gradeitem = $assign->get_grade_item(); 2706 $gradeitem->gradepass = '0'; 2707 $gradeitem->update(); 2708 2709 // Student should be able to see an add submission button. 2710 $this->setUser($student); 2711 $output = $assign->view_student_summary($student, true); 2712 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign'))); 2713 2714 // Add a submission. 2715 $this->add_submission($student, $assign); 2716 $this->submit_for_grading($student, $assign); 2717 2718 // Mark the submission with any grade. 2719 $this->mark_submission($teacher, $assign, $student, 0.0); 2720 2721 // Check the student can see the grade. 2722 $this->setUser($student); 2723 $output = $assign->view_student_summary($student, true); 2724 $this->assertNotEquals(false, strpos($output, '0.0')); 2725 2726 // Check that the student does not have a button for Add a new attempt. 2727 $output = $assign->view_student_summary($student, true); 2728 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign'))); 2729 } 2730 2731 /** 2732 * Test student visibility for each stage of the marking workflow. 2733 */ 2734 public function test_markingworkflow() { 2735 global $PAGE; 2736 2737 $this->resetAfterTest(); 2738 $course = $this->getDataGenerator()->create_course(); 2739 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2740 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2741 2742 $assign = $this->create_instance($course, [ 2743 'markingworkflow' => 1, 2744 ]); 2745 2746 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2747 2748 // Mark the submission and set to notmarked. 2749 $this->mark_submission($teacher, $assign, $student, 50.0, [ 2750 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED, 2751 ]); 2752 2753 // Check the student can't see the grade. 2754 $this->setUser($student); 2755 $output = $assign->view_student_summary($student, true); 2756 $this->assertEquals(false, strpos($output, '50.0')); 2757 2758 // Make sure the grade isn't pushed to the gradebook. 2759 $grades = $assign->get_user_grades_for_gradebook($student->id); 2760 $this->assertEmpty($grades); 2761 2762 // Mark the submission and set to inmarking. 2763 $this->mark_submission($teacher, $assign, $student, 50.0, [ 2764 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING, 2765 ]); 2766 2767 // Check the student can't see the grade. 2768 $this->setUser($student); 2769 $output = $assign->view_student_summary($student, true); 2770 $this->assertEquals(false, strpos($output, '50.0')); 2771 2772 // Make sure the grade isn't pushed to the gradebook. 2773 $grades = $assign->get_user_grades_for_gradebook($student->id); 2774 $this->assertEmpty($grades); 2775 2776 // Mark the submission and set to readyforreview. 2777 $this->mark_submission($teacher, $assign, $student, 50.0, [ 2778 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW, 2779 ]); 2780 2781 // Check the student can't see the grade. 2782 $this->setUser($student); 2783 $output = $assign->view_student_summary($student, true); 2784 $this->assertEquals(false, strpos($output, '50.0')); 2785 2786 // Make sure the grade isn't pushed to the gradebook. 2787 $grades = $assign->get_user_grades_for_gradebook($student->id); 2788 $this->assertEmpty($grades); 2789 2790 // Mark the submission and set to inreview. 2791 $this->mark_submission($teacher, $assign, $student, 50.0, [ 2792 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW, 2793 ]); 2794 2795 // Check the student can't see the grade. 2796 $this->setUser($student); 2797 $output = $assign->view_student_summary($student, true); 2798 $this->assertEquals(false, strpos($output, '50.0')); 2799 2800 // Make sure the grade isn't pushed to the gradebook. 2801 $grades = $assign->get_user_grades_for_gradebook($student->id); 2802 $this->assertEmpty($grades); 2803 2804 // Mark the submission and set to readyforrelease. 2805 $this->mark_submission($teacher, $assign, $student, 50.0, [ 2806 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE, 2807 ]); 2808 2809 // Check the student can't see the grade. 2810 $this->setUser($student); 2811 $output = $assign->view_student_summary($student, true); 2812 $this->assertEquals(false, strpos($output, '50.0')); 2813 2814 // Make sure the grade isn't pushed to the gradebook. 2815 $grades = $assign->get_user_grades_for_gradebook($student->id); 2816 $this->assertEmpty($grades); 2817 2818 // Mark the submission and set to released. 2819 $this->mark_submission($teacher, $assign, $student, 50.0, [ 2820 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED, 2821 ]); 2822 2823 // Check the student can see the grade. 2824 $this->setUser($student); 2825 $output = $assign->view_student_summary($student, true); 2826 $this->assertNotEquals(false, strpos($output, '50.0')); 2827 2828 // Make sure the grade is pushed to the gradebook. 2829 $grades = $assign->get_user_grades_for_gradebook($student->id); 2830 $this->assertEquals(50, (int)$grades[$student->id]->rawgrade); 2831 } 2832 2833 /** 2834 * Test that a student allocated a specific marker is only shown to that marker. 2835 */ 2836 public function test_markerallocation() { 2837 global $PAGE; 2838 2839 $this->resetAfterTest(); 2840 $course = $this->getDataGenerator()->create_course(); 2841 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2842 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2843 $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2844 2845 $assign = $this->create_instance($course, [ 2846 'markingworkflow' => 1, 2847 'markingallocation' => 1 2848 ]); 2849 2850 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2851 2852 // Allocate marker to submission. 2853 $this->mark_submission($teacher, $assign, $student, null, [ 2854 'allocatedmarker' => $teacher->id, 2855 ]); 2856 2857 // Check the allocated marker can view the submission. 2858 $this->setUser($teacher); 2859 $users = $assign->list_participants(0, true); 2860 $this->assertEquals(1, count($users)); 2861 $this->assertTrue(isset($users[$student->id])); 2862 2863 $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id); 2864 $context = context_module::instance($cm->id); 2865 $assign = new mod_assign_testable_assign($context, $cm, $course); 2866 2867 // Check that other teachers can't view this submission. 2868 $this->setUser($otherteacher); 2869 $users = $assign->list_participants(0, true); 2870 $this->assertEquals(0, count($users)); 2871 } 2872 2873 /** 2874 * Ensure that a teacher cannot submit for students as standard. 2875 */ 2876 public function test_teacher_submit_for_student() { 2877 global $PAGE; 2878 2879 $this->resetAfterTest(); 2880 $course = $this->getDataGenerator()->create_course(); 2881 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2882 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2883 2884 $assign = $this->create_instance($course, [ 2885 'assignsubmission_onlinetext_enabled' => 1, 2886 'submissiondrafts' => 1, 2887 ]); 2888 2889 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2890 2891 // Add a submission but do not submit. 2892 $this->add_submission($student, $assign, 'Student submission text'); 2893 2894 $this->setUser($student); 2895 $output = $assign->view_student_summary($student, true); 2896 $this->assertContains('Student submission text', $output, 'Contains student submission text'); 2897 2898 // Check that a teacher can not edit the submission as they do not have the capability. 2899 $this->setUser($teacher); 2900 $this->expectException('moodle_exception'); 2901 $this->expectExceptionMessage('error/nopermission'); 2902 $this->add_submission($student, $assign, 'Teacher edited submission text', false); 2903 } 2904 2905 /** 2906 * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student. 2907 */ 2908 public function test_teacher_submit_for_student_with_capability() { 2909 global $PAGE; 2910 2911 $this->resetAfterTest(); 2912 $course = $this->getDataGenerator()->create_course(); 2913 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2914 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2915 $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2916 2917 $assign = $this->create_instance($course, [ 2918 'assignsubmission_onlinetext_enabled' => 1, 2919 'submissiondrafts' => 1, 2920 ]); 2921 2922 // Add the required capability. 2923 $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description'); 2924 assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id); 2925 role_assign($roleid, $teacher->id, $assign->get_context()->id); 2926 accesslib_clear_all_caches_for_unit_testing(); 2927 2928 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2929 2930 // Add a submission but do not submit. 2931 $this->add_submission($student, $assign, 'Student submission text'); 2932 2933 $this->setUser($student); 2934 $output = $assign->view_student_summary($student, true); 2935 $this->assertContains('Student submission text', $output, 'Contains student submission text'); 2936 2937 // Check that a teacher can edit the submission. 2938 $this->setUser($teacher); 2939 $this->add_submission($student, $assign, 'Teacher edited submission text', false); 2940 2941 $this->setUser($student); 2942 $output = $assign->view_student_summary($student, true); 2943 $this->assertNotContains('Student submission text', $output, 'Contains student submission text'); 2944 $this->assertContains('Teacher edited submission text', $output, 'Contains teacher edited submission text'); 2945 2946 // Check that the teacher can submit the students work. 2947 $this->setUser($teacher); 2948 $this->submit_for_grading($student, $assign, [], false); 2949 2950 // Revert to draft so the student can edit it. 2951 $assign->revert_to_draft($student->id); 2952 2953 $this->setUser($student); 2954 2955 // Check that the submission text was saved. 2956 $output = $assign->view_student_summary($student, true); 2957 $this->assertContains('Teacher edited submission text', $output, 'Contains student submission text'); 2958 2959 // Check that the student can submit their work. 2960 $this->submit_for_grading($student, $assign, []); 2961 2962 $output = $assign->view_student_summary($student, true); 2963 $this->assertNotContains(get_string('addsubmission', 'assign'), $output); 2964 2965 // An editing teacher without the extra role should still be able to revert to draft. 2966 $this->setUser($otherteacher); 2967 2968 // Revert to draft so the submission is editable. 2969 $assign->revert_to_draft($student->id); 2970 } 2971 2972 /** 2973 * Ensure that disabling submit after the cutoff date works as expected. 2974 */ 2975 public function test_disable_submit_after_cutoff_date() { 2976 global $PAGE; 2977 2978 $this->resetAfterTest(); 2979 $course = $this->getDataGenerator()->create_course(); 2980 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2981 2982 $now = time(); 2983 $tomorrow = $now + DAYSECS; 2984 $lastweek = $now - (7 * DAYSECS); 2985 $yesterday = $now - DAYSECS; 2986 2987 $this->setAdminUser(); 2988 $assign = $this->create_instance($course, [ 2989 'duedate' => $yesterday, 2990 'cutoffdate' => $tomorrow, 2991 'assignsubmission_onlinetext_enabled' => 1, 2992 ]); 2993 2994 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2995 2996 // Student should be able to see an add submission button. 2997 $this->setUser($student); 2998 $output = $assign->view_student_summary($student, true); 2999 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign'))); 3000 3001 // Add a submission but don't submit now. 3002 $this->add_submission($student, $assign); 3003 3004 // Create another instance with cut-off and due-date already passed. 3005 $this->setAdminUser(); 3006 $assign = $this->create_instance($course, [ 3007 'duedate' => $lastweek, 3008 'cutoffdate' => $yesterday, 3009 'assignsubmission_onlinetext_enabled' => 1, 3010 ]); 3011 3012 $this->setUser($student); 3013 $output = $assign->view_student_summary($student, true); 3014 $this->assertNotContains($output, get_string('editsubmission', 'assign'), 3015 'Should not be able to edit after cutoff date.'); 3016 $this->assertNotContains($output, get_string('submitassignment', 'assign'), 3017 'Should not be able to submit after cutoff date.'); 3018 } 3019 3020 /** 3021 * Testing for submission comment plugin settings. 3022 * 3023 * @dataProvider submission_plugin_settings_provider 3024 * @param bool $globalenabled 3025 * @param array $instanceconfig 3026 * @param bool $isenabled 3027 */ 3028 public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled) { 3029 global $CFG; 3030 3031 $this->resetAfterTest(); 3032 $course = $this->getDataGenerator()->create_course(); 3033 3034 $CFG->usecomments = $globalenabled; 3035 $assign = $this->create_instance($course, $instanceconfig); 3036 $plugin = $assign->get_submission_plugin_by_type('comments'); 3037 $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled')); 3038 } 3039 3040 public function submission_plugin_settings_provider() { 3041 return [ 3042 'CFG->usecomments true, empty config => Enabled by default' => [ 3043 true, 3044 [], 3045 true, 3046 ], 3047 'CFG->usecomments true, config enabled => Comments enabled' => [ 3048 true, 3049 [ 3050 'assignsubmission_comments_enabled' => 1, 3051 ], 3052 true, 3053 ], 3054 'CFG->usecomments true, config idisabled => Comments enabled' => [ 3055 true, 3056 [ 3057 'assignsubmission_comments_enabled' => 0, 3058 ], 3059 true, 3060 ], 3061 'CFG->usecomments false, empty config => Disabled by default' => [ 3062 false, 3063 [], 3064 false, 3065 ], 3066 'CFG->usecomments false, config enabled => Comments disabled' => [ 3067 false, 3068 [ 3069 'assignsubmission_comments_enabled' => 1, 3070 ], 3071 false, 3072 ], 3073 'CFG->usecomments false, config disabled => Comments disabled' => [ 3074 false, 3075 [ 3076 'assignsubmission_comments_enabled' => 0, 3077 ], 3078 false, 3079 ], 3080 ]; 3081 } 3082 3083 /** 3084 * Testing for comment inline settings 3085 */ 3086 public function test_feedback_comment_commentinline() { 3087 global $CFG, $USER; 3088 3089 $this->resetAfterTest(); 3090 $course = $this->getDataGenerator()->create_course(); 3091 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3092 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3093 3094 $sourcetext = "Hello! 3095 3096 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness. 3097 3098 URL outside a tag: https://moodle.org/logo/logo-240x60.gif 3099 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif 3100 3101 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/> 3102 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/> 3103 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/> 3104 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/> 3105 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a> 3106 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a> 3107 "; 3108 3109 $this->setUser($teacher); 3110 $assign = $this->create_instance($course, [ 3111 'assignsubmission_onlinetext_enabled' => 1, 3112 'assignfeedback_comments_enabled' => 1, 3113 'assignfeedback_comments_commentinline' => 1, 3114 ]); 3115 3116 $this->setUser($student); 3117 3118 // Add a submission but don't submit now. 3119 $this->add_submission($student, $assign, $sourcetext); 3120 3121 $this->setUser($teacher); 3122 3123 $data = new stdClass(); 3124 require_once($CFG->dirroot . '/mod/assign/gradeform.php'); 3125 $pagination = [ 3126 'userid' => $student->id, 3127 'rownum' => 0, 3128 'last' => true, 3129 'useridlistid' => $assign->get_useridlist_key_id(), 3130 'attemptnumber' => 0, 3131 ]; 3132 $formparams = array($assign, $data, $pagination); 3133 $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]); 3134 3135 // We need to get the URL these will be transformed to. 3136 $context = context_user::instance($USER->id); 3137 $itemid = $data->assignfeedbackcomments_editor['itemid']; 3138 $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid; 3139 3140 // Note the internal images have been stripped and the html is purified (quotes fixed in this case). 3141 $filteredtext = "Hello! 3142 3143 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness. 3144 3145 URL outside a tag: https://moodle.org/logo/logo-240x60.gif 3146 Plugin url outside a tag: $url/logo-240x60.gif 3147 3148 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" /> 3149 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" /> 3150 Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" /> 3151 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" /> 3152 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a> 3153 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a> 3154 "; 3155 3156 $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']); 3157 } 3158 3159 /** 3160 * Testing for feedback comment plugin settings. 3161 * 3162 * @dataProvider feedback_plugin_settings_provider 3163 * @param array $instanceconfig 3164 * @param bool $isenabled 3165 */ 3166 public function test_feedback_plugin_settings($instanceconfig, $isenabled) { 3167 $this->resetAfterTest(); 3168 $course = $this->getDataGenerator()->create_course(); 3169 3170 $assign = $this->create_instance($course, $instanceconfig); 3171 $plugin = $assign->get_feedback_plugin_by_type('comments'); 3172 $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled')); 3173 } 3174 3175 public function feedback_plugin_settings_provider() { 3176 return [ 3177 'No configuration => disabled' => [ 3178 [], 3179 false, 3180 ], 3181 'Actively disabled' => [ 3182 [ 3183 'assignfeedback_comments_enabled' => 0, 3184 ], 3185 false, 3186 ], 3187 'Actively enabled' => [ 3188 [ 3189 'assignfeedback_comments_enabled' => 1, 3190 ], 3191 true, 3192 ], 3193 ]; 3194 } 3195 3196 /** 3197 * Testing if gradebook feedback plugin is enabled. 3198 */ 3199 public function test_is_gradebook_feedback_enabled() { 3200 $this->resetAfterTest(); 3201 $course = $this->getDataGenerator()->create_course(); 3202 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3203 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3204 3205 $adminconfig = get_config('assign'); 3206 $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook; 3207 3208 // Create assignment with gradebook feedback enabled and grade = 0. 3209 $assign = $this->create_instance($course, [ 3210 "{$gradebookplugin}_enabled" => 1, 3211 'grades' => 0, 3212 ]); 3213 3214 // Get gradebook feedback plugin. 3215 $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin); 3216 $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype); 3217 $this->assertEquals(1, $plugin->is_enabled('enabled')); 3218 $this->assertEquals(1, $assign->is_gradebook_feedback_enabled()); 3219 } 3220 3221 /** 3222 * Testing if gradebook feedback plugin is disabled. 3223 */ 3224 public function test_is_gradebook_feedback_disabled() { 3225 $this->resetAfterTest(); 3226 $course = $this->getDataGenerator()->create_course(); 3227 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3228 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3229 3230 $adminconfig = get_config('assign'); 3231 $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook; 3232 3233 // Create assignment with gradebook feedback disabled and grade = 0. 3234 $assign = $this->create_instance($course, [ 3235 "{$gradebookplugin}_enabled" => 0, 3236 'grades' => 0, 3237 ]); 3238 3239 $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin); 3240 $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype); 3241 $this->assertEquals(0, $plugin->is_enabled('enabled')); 3242 } 3243 3244 /** 3245 * Testing can_edit_submission. 3246 */ 3247 public function test_can_edit_submission() { 3248 $this->resetAfterTest(); 3249 $course = $this->getDataGenerator()->create_course(); 3250 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3251 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3252 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3253 3254 $assign = $this->create_instance($course, [ 3255 'assignsubmission_onlinetext_enabled' => 1, 3256 'submissiondrafts' => 1, 3257 ]); 3258 3259 // Check student can edit their own submission. 3260 $this->assertTrue($assign->can_edit_submission($student->id, $student->id)); 3261 3262 // Check student cannot edit others submission. 3263 $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id)); 3264 3265 // Check teacher cannot (by default) edit a students submission. 3266 $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id)); 3267 } 3268 3269 /** 3270 * Testing can_edit_submission with the editothersubmission capability. 3271 */ 3272 public function test_can_edit_submission_with_editothersubmission() { 3273 $this->resetAfterTest(); 3274 $course = $this->getDataGenerator()->create_course(); 3275 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3276 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3277 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3278 3279 $assign = $this->create_instance($course, [ 3280 'assignsubmission_onlinetext_enabled' => 1, 3281 'submissiondrafts' => 1, 3282 ]); 3283 3284 // Add the required capability to edit a student submission. 3285 $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description'); 3286 assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id); 3287 role_assign($roleid, $teacher->id, $assign->get_context()->id); 3288 accesslib_clear_all_caches_for_unit_testing(); 3289 3290 // Check student can edit their own submission. 3291 $this->assertTrue($assign->can_edit_submission($student->id, $student->id)); 3292 3293 // Check student cannot edit others submission. 3294 $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id)); 3295 3296 // Retest - should now have access. 3297 $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id)); 3298 } 3299 3300 /** 3301 * Testing can_edit_submission 3302 */ 3303 public function test_can_edit_submission_separategroups() { 3304 $this->resetAfterTest(); 3305 $course = $this->getDataGenerator()->create_course(); 3306 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3307 3308 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3309 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3310 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3311 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3312 3313 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 3314 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3315 groups_assign_grouping($grouping->id, $group1->id); 3316 groups_add_member($group1, $student1); 3317 groups_add_member($group1, $student2); 3318 3319 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3320 groups_assign_grouping($grouping->id, $group2->id); 3321 groups_add_member($group2, $student3); 3322 groups_add_member($group2, $student4); 3323 3324 $assign = $this->create_instance($course, [ 3325 'assignsubmission_onlinetext_enabled' => 1, 3326 'submissiondrafts' => 1, 3327 'groupingid' => $grouping->id, 3328 'groupmode' => SEPARATEGROUPS, 3329 ]); 3330 3331 // Verify a student does not have the ability to edit submissions for other users. 3332 $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id)); 3333 $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id)); 3334 $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id)); 3335 $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id)); 3336 } 3337 3338 /** 3339 * Testing can_edit_submission 3340 */ 3341 public function test_can_edit_submission_separategroups_with_editothersubmission() { 3342 $this->resetAfterTest(); 3343 $course = $this->getDataGenerator()->create_course(); 3344 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3345 3346 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3347 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3348 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3349 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3350 3351 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 3352 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3353 groups_assign_grouping($grouping->id, $group1->id); 3354 groups_add_member($group1, $student1); 3355 groups_add_member($group1, $student2); 3356 3357 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3358 groups_assign_grouping($grouping->id, $group2->id); 3359 groups_add_member($group2, $student3); 3360 groups_add_member($group2, $student4); 3361 3362 $assign = $this->create_instance($course, [ 3363 'assignsubmission_onlinetext_enabled' => 1, 3364 'submissiondrafts' => 1, 3365 'groupingid' => $grouping->id, 3366 'groupmode' => SEPARATEGROUPS, 3367 ]); 3368 3369 // Add the capability to the new assignment for student 1. 3370 $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description'); 3371 assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id); 3372 role_assign($roleid, $student1->id, $assign->get_context()->id); 3373 accesslib_clear_all_caches_for_unit_testing(); 3374 3375 // Verify student1 has the ability to edit submissions for other users in their group, but not other groups. 3376 $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id)); 3377 $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id)); 3378 $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id)); 3379 $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id)); 3380 3381 // Verify other students do not have the ability to edit submissions for other users. 3382 $this->assertTrue($assign->can_edit_submission($student2->id, $student2->id)); 3383 $this->assertFalse($assign->can_edit_submission($student1->id, $student2->id)); 3384 $this->assertFalse($assign->can_edit_submission($student3->id, $student2->id)); 3385 $this->assertFalse($assign->can_edit_submission($student4->id, $student2->id)); 3386 } 3387 3388 /** 3389 * Test if the view blind details capability works 3390 */ 3391 public function test_can_view_blind_details() { 3392 global $DB; 3393 3394 $this->resetAfterTest(); 3395 $course = $this->getDataGenerator()->create_course(); 3396 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3397 $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager'); 3398 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3399 3400 $assign = $this->create_instance($course, [ 3401 'blindmarking' => 1, 3402 ]); 3403 3404 $this->assertTrue($assign->is_blind_marking()); 3405 3406 // Test student names are hidden to teacher. 3407 $this->setUser($teacher); 3408 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 3409 $output = $assign->get_renderer()->render($gradingtable); 3410 $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign'))); // "Participant" is somewhere on the page. 3411 $this->assertEquals(false, strpos($output, fullname($student))); // Students full name doesn't appear. 3412 3413 // Test student names are visible to manager. 3414 $this->setUser($manager); 3415 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 3416 $output = $assign->get_renderer()->render($gradingtable); 3417 $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign'))); 3418 $this->assertEquals(true, strpos($output, fullname($student))); 3419 } 3420 3421 /** 3422 * Testing get_shared_group_members 3423 */ 3424 public function test_get_shared_group_members() { 3425 $this->resetAfterTest(); 3426 $course = $this->getDataGenerator()->create_course(); 3427 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3428 3429 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3430 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3431 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3432 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3433 3434 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 3435 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3436 groups_assign_grouping($grouping->id, $group1->id); 3437 groups_add_member($group1, $student1); 3438 groups_add_member($group1, $student2); 3439 3440 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3441 groups_assign_grouping($grouping->id, $group2->id); 3442 groups_add_member($group2, $student3); 3443 groups_add_member($group2, $student4); 3444 3445 $assign = $this->create_instance($course, [ 3446 'groupingid' => $grouping->id, 3447 'groupmode' => SEPARATEGROUPS, 3448 ]); 3449 3450 $cm = $assign->get_course_module(); 3451 3452 // Get shared group members for students 0 and 1. 3453 $groupmembers = $assign->get_shared_group_members($cm, $student1->id); 3454 $this->assertCount(2, $groupmembers); 3455 $this->assertContains($student1->id, $groupmembers); 3456 $this->assertContains($student2->id, $groupmembers); 3457 3458 $groupmembers = $assign->get_shared_group_members($cm, $student2->id); 3459 $this->assertCount(2, $groupmembers); 3460 $this->assertContains($student1->id, $groupmembers); 3461 $this->assertContains($student2->id, $groupmembers); 3462 3463 $groupmembers = $assign->get_shared_group_members($cm, $student3->id); 3464 $this->assertCount(2, $groupmembers); 3465 $this->assertContains($student3->id, $groupmembers); 3466 $this->assertContains($student4->id, $groupmembers); 3467 3468 $groupmembers = $assign->get_shared_group_members($cm, $student4->id); 3469 $this->assertCount(2, $groupmembers); 3470 $this->assertContains($student3->id, $groupmembers); 3471 $this->assertContains($student4->id, $groupmembers); 3472 } 3473 3474 /** 3475 * Testing get_shared_group_members 3476 */ 3477 public function test_get_shared_group_members_override() { 3478 $this->resetAfterTest(); 3479 $course = $this->getDataGenerator()->create_course(); 3480 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3481 3482 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3483 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3484 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3485 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3486 3487 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 3488 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3489 groups_assign_grouping($grouping->id, $group1->id); 3490 groups_add_member($group1, $student1); 3491 groups_add_member($group1, $student2); 3492 3493 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3494 groups_assign_grouping($grouping->id, $group2->id); 3495 groups_add_member($group2, $student3); 3496 groups_add_member($group2, $student4); 3497 3498 $assign = $this->create_instance($course, [ 3499 'groupingid' => $grouping->id, 3500 'groupmode' => SEPARATEGROUPS, 3501 ]); 3502 3503 $cm = $assign->get_course_module(); 3504 3505 // Add the capability to access allgroups for one of the students. 3506 $roleid = create_role('Access all groups role', 'accessallgroupsrole', ''); 3507 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $assign->get_context()->id); 3508 role_assign($roleid, $student1->id, $assign->get_context()->id); 3509 accesslib_clear_all_caches_for_unit_testing(); 3510 3511 // Get shared group members for students 0 and 1. 3512 $groupmembers = $assign->get_shared_group_members($cm, $student1->id); 3513 $this->assertCount(4, $groupmembers); 3514 $this->assertContains($student1->id, $groupmembers); 3515 $this->assertContains($student2->id, $groupmembers); 3516 $this->assertContains($student3->id, $groupmembers); 3517 $this->assertContains($student4->id, $groupmembers); 3518 3519 $groupmembers = $assign->get_shared_group_members($cm, $student2->id); 3520 $this->assertCount(2, $groupmembers); 3521 $this->assertContains($student1->id, $groupmembers); 3522 $this->assertContains($student2->id, $groupmembers); 3523 3524 $groupmembers = $assign->get_shared_group_members($cm, $student3->id); 3525 $this->assertCount(2, $groupmembers); 3526 $this->assertContains($student3->id, $groupmembers); 3527 $this->assertContains($student4->id, $groupmembers); 3528 3529 $groupmembers = $assign->get_shared_group_members($cm, $student4->id); 3530 $this->assertCount(2, $groupmembers); 3531 $this->assertContains($student3->id, $groupmembers); 3532 $this->assertContains($student4->id, $groupmembers); 3533 } 3534 3535 /** 3536 * Test get plugins file areas 3537 */ 3538 public function test_get_plugins_file_areas() { 3539 global $DB; 3540 3541 $this->resetAfterTest(); 3542 $course = $this->getDataGenerator()->create_course(); 3543 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3544 3545 $assign = $this->create_instance($course); 3546 3547 // Test that all the submission and feedback plugins are returning the expected file aras. 3548 $usingfilearea = 0; 3549 $coreplugins = core_plugin_manager::standard_plugins_list('assignsubmission'); 3550 foreach ($assign->get_submission_plugins() as $plugin) { 3551 $type = $plugin->get_type(); 3552 if (!in_array($type, $coreplugins)) { 3553 continue; 3554 } 3555 $fileareas = $plugin->get_file_areas(); 3556 3557 if ($type == 'onlinetext') { 3558 $this->assertEquals(array('submissions_onlinetext' => 'Online text'), $fileareas); 3559 $usingfilearea++; 3560 } else if ($type == 'file') { 3561 $this->assertEquals(array('submission_files' => 'File submissions'), $fileareas); 3562 $usingfilearea++; 3563 } else { 3564 $this->assertEmpty($fileareas); 3565 } 3566 } 3567 $this->assertEquals(2, $usingfilearea); 3568 3569 $usingfilearea = 0; 3570 $coreplugins = core_plugin_manager::standard_plugins_list('assignfeedback'); 3571 foreach ($assign->get_feedback_plugins() as $plugin) { 3572 $type = $plugin->get_type(); 3573 if (!in_array($type, $coreplugins)) { 3574 continue; 3575 } 3576 $fileareas = $plugin->get_file_areas(); 3577 3578 if ($type == 'editpdf') { 3579 $this->assertEquals(array('download' => 'Annotate PDF'), $fileareas); 3580 $usingfilearea++; 3581 } else if ($type == 'file') { 3582 $this->assertEquals(array('feedback_files' => 'Feedback files'), $fileareas); 3583 $usingfilearea++; 3584 } else if ($type == 'comments') { 3585 $this->assertEquals(array('feedback' => 'Feedback comments'), $fileareas); 3586 $usingfilearea++; 3587 } else { 3588 $this->assertEmpty($fileareas); 3589 } 3590 } 3591 $this->assertEquals(3, $usingfilearea); 3592 } 3593 3594 /** 3595 * Test override exists 3596 * 3597 * This function needs to obey the group override logic as per the assign grading table and 3598 * the overview block. 3599 */ 3600 public function test_override_exists() { 3601 global $DB; 3602 3603 $this->resetAfterTest(); 3604 $course = $this->getDataGenerator()->create_course(); 3605 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3606 3607 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 3608 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3609 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3610 3611 3612 // Data: 3613 // - student1 => group A only 3614 // - student2 => group B only 3615 // - student3 => Group A + Group B (No user override) 3616 // - student4 => Group A + Group B (With user override) 3617 // - student4 => No groups. 3618 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3619 groups_add_member($group1, $student1); 3620 3621 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3622 groups_add_member($group2, $student2); 3623 3624 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3625 groups_add_member($group1, $student3); 3626 groups_add_member($group2, $student3); 3627 3628 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3629 groups_add_member($group1, $student4); 3630 groups_add_member($group2, $student4); 3631 3632 $student5 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3633 3634 $assign = $this->create_instance($course); 3635 $instance = $assign->get_instance(); 3636 3637 // Overrides for each of the groups, and a user override. 3638 $overrides = [ 3639 (object) [ 3640 // Override for group 1, highest priority (numerically lowest sortorder). 3641 'assignid' => $instance->id, 3642 'groupid' => $group1->id, 3643 'userid' => null, 3644 'sortorder' => 1, 3645 'allowsubmissionsfromdate' => 1, 3646 'duedate' => 2, 3647 'cutoffdate' => 3 3648 ], 3649 (object) [ 3650 // Override for group 2, lower priority (numerically higher sortorder). 3651 'assignid' => $instance->id, 3652 'groupid' => $group2->id, 3653 'userid' => null, 3654 'sortorder' => 2, 3655 'allowsubmissionsfromdate' => 5, 3656 'duedate' => 6, 3657 'cutoffdate' => 6 3658 ], 3659 (object) [ 3660 // User override. 3661 'assignid' => $instance->id, 3662 'groupid' => null, 3663 'userid' => $student3->id, 3664 'sortorder' => null, 3665 'allowsubmissionsfromdate' => 7, 3666 'duedate' => 8, 3667 'cutoffdate' => 9 3668 ], 3669 ]; 3670 3671 foreach ($overrides as &$override) { 3672 $override->id = $DB->insert_record('assign_overrides', $override); 3673 } 3674 3675 // User only in group 1 should see the group 1 override. 3676 $this->assertEquals($overrides[0], $assign->override_exists($student1->id)); 3677 3678 // User only in group 2 should see the group 2 override. 3679 $this->assertEquals($overrides[1], $assign->override_exists($student2->id)); 3680 3681 // User only in both groups with an override should see the user override as it has higher priority. 3682 $this->assertEquals($overrides[2], $assign->override_exists($student3->id)); 3683 3684 // User only in both groups with no override should see the group 1 override as it has higher priority. 3685 $this->assertEquals($overrides[0], $assign->override_exists($student4->id)); 3686 3687 // User with no overrides shoudl get nothing. 3688 $override = $assign->override_exists($student5->id); 3689 $this->assertNull($override->duedate); 3690 $this->assertNull($override->cutoffdate); 3691 $this->assertNull($override->allowsubmissionsfromdate); 3692 } 3693 3694 /** 3695 * Test the quicksave grades processor 3696 */ 3697 public function test_process_save_quick_grades() { 3698 $this->resetAfterTest(); 3699 $course = $this->getDataGenerator()->create_course(); 3700 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3701 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3702 3703 $teacher->ignoresesskey = true; 3704 $this->setUser($teacher); 3705 $assign = $this->create_instance($course, [ 3706 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL, 3707 ]); 3708 3709 // Initially grade the user. 3710 $grade = (object) [ 3711 'attemptnumber' => '', 3712 'timemodified' => '', 3713 ]; 3714 $data = [ 3715 "grademodified_{$student->id}" => $grade->timemodified, 3716 "gradeattempt_{$student->id}" => $grade->attemptnumber, 3717 "quickgrade_{$student->id}" => '60.0', 3718 ]; 3719 3720 $result = $assign->testable_process_save_quick_grades($data); 3721 $this->assertContains(get_string('quickgradingchangessaved', 'assign'), $result); 3722 $grade = $assign->get_user_grade($student->id, false); 3723 $this->assertEquals(60.0, $grade->grade); 3724 3725 // Attempt to grade with a past attempts grade info. 3726 $assign->testable_process_add_attempt($student->id); 3727 $data = array( 3728 'grademodified_' . $student->id => $grade->timemodified, 3729 'gradeattempt_' . $student->id => $grade->attemptnumber, 3730 'quickgrade_' . $student->id => '50.0' 3731 ); 3732 $result = $assign->testable_process_save_quick_grades($data); 3733 $this->assertContains(get_string('errorrecordmodified', 'assign'), $result); 3734 $grade = $assign->get_user_grade($student->id, false); 3735 $this->assertFalse($grade); 3736 3737 // Attempt to grade a the attempt. 3738 $submission = $assign->get_user_submission($student->id, false); 3739 $data = array( 3740 'grademodified_' . $student->id => '', 3741 'gradeattempt_' . $student->id => $submission->attemptnumber, 3742 'quickgrade_' . $student->id => '40.0' 3743 ); 3744 $result = $assign->testable_process_save_quick_grades($data); 3745 $this->assertContains(get_string('quickgradingchangessaved', 'assign'), $result); 3746 $grade = $assign->get_user_grade($student->id, false); 3747 $this->assertEquals(40.0, $grade->grade); 3748 3749 // Catch grade update conflicts. 3750 // Save old data for later. 3751 $pastdata = $data; 3752 // Update the grade the 'good' way. 3753 $data = array( 3754 'grademodified_' . $student->id => $grade->timemodified, 3755 'gradeattempt_' . $student->id => $grade->attemptnumber, 3756 'quickgrade_' . $student->id => '30.0' 3757 ); 3758 $result = $assign->testable_process_save_quick_grades($data); 3759 $this->assertContains(get_string('quickgradingchangessaved', 'assign'), $result); 3760 $grade = $assign->get_user_grade($student->id, false); 3761 $this->assertEquals(30.0, $grade->grade); 3762 3763 // Now update using 'old' data. Should fail. 3764 $result = $assign->testable_process_save_quick_grades($pastdata); 3765 $this->assertContains(get_string('errorrecordmodified', 'assign'), $result); 3766 $grade = $assign->get_user_grade($student->id, false); 3767 $this->assertEquals(30.0, $grade->grade); 3768 } 3769 3770 /** 3771 * Test updating activity completion when submitting an assessment. 3772 */ 3773 public function test_update_activity_completion_records_solitary_submission() { 3774 $this->resetAfterTest(); 3775 3776 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); 3777 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3778 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3779 3780 $this->setUser($teacher); 3781 $assign = $this->create_instance($course, [ 3782 'grade' => 100, 3783 'completion' => COMPLETION_TRACKING_AUTOMATIC, 3784 'requireallteammemberssubmit' => 0, 3785 ]); 3786 $cm = $assign->get_course_module(); 3787 3788 // Submit the assignment as the student. 3789 $this->add_submission($student, $assign); 3790 3791 // Check that completion is not met yet. 3792 $completion = new completion_info($course); 3793 $completiondata = $completion->get_data($cm, false, $student->id); 3794 $this->assertEquals(0, $completiondata->completionstate); 3795 3796 // Update to mark as complete. 3797 $submission = $assign->get_user_submission($student->id, true); 3798 $assign->testable_update_activity_completion_records(0, 0, $submission, 3799 $student->id, COMPLETION_COMPLETE, $completion); 3800 3801 // Completion should now be met. 3802 $completiondata = $completion->get_data($cm, false, $student->id); 3803 $this->assertEquals(1, $completiondata->completionstate); 3804 } 3805 3806 /** 3807 * Test updating activity completion when submitting an assessment. 3808 */ 3809 public function test_update_activity_completion_records_team_submission() { 3810 $this->resetAfterTest(); 3811 3812 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); 3813 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3814 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3815 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3816 3817 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 3818 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3819 3820 groups_add_member($group1, $student); 3821 groups_add_member($group1, $otherstudent); 3822 3823 $assign = $this->create_instance($course, [ 3824 'grade' => 100, 3825 'completion' => COMPLETION_TRACKING_AUTOMATIC, 3826 'teamsubmission' => 1, 3827 ]); 3828 3829 $cm = $assign->get_course_module(); 3830 3831 $this->add_submission($student, $assign); 3832 $this->submit_for_grading($student, $assign, ['groupid' => $group1->id]); 3833 3834 $completion = new completion_info($course); 3835 3836 // Check that completion is not met yet. 3837 $completiondata = $completion->get_data($cm, false, $student->id); 3838 $this->assertEquals(0, $completiondata->completionstate); 3839 3840 $completiondata = $completion->get_data($cm, false, $otherstudent->id); 3841 $this->assertEquals(0, $completiondata->completionstate); 3842 3843 $submission = $assign->get_user_submission($student->id, true); 3844 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 3845 $submission->groupid = $group1->id; 3846 3847 $assign->testable_update_activity_completion_records(1, 0, $submission, $student->id, COMPLETION_COMPLETE, $completion); 3848 3849 // Completion should now be met. 3850 $completiondata = $completion->get_data($cm, false, $student->id); 3851 $this->assertEquals(1, $completiondata->completionstate); 3852 3853 $completiondata = $completion->get_data($cm, false, $otherstudent->id); 3854 $this->assertEquals(1, $completiondata->completionstate); 3855 } 3856 3857 /** 3858 * Test updating activity completion when submitting an assessment for MDL-67126. 3859 */ 3860 public function test_update_activity_completion_records_team_submission_new() { 3861 $this->resetAfterTest(); 3862 3863 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); 3864 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3865 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3866 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3867 3868 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 3869 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 3870 3871 groups_add_member($group1, $student); 3872 groups_add_member($group1, $otherstudent); 3873 3874 $assign = $this->create_instance($course, [ 3875 'submissiondrafts' => 0, 3876 'completion' => COMPLETION_TRACKING_AUTOMATIC, 3877 'completionsubmit' => 1, 3878 'teamsubmission' => 1, 3879 'assignsubmission_onlinetext_enabled' => 1 3880 ]); 3881 3882 $cm = $assign->get_course_module(); 3883 3884 $this->add_submission($student, $assign); 3885 3886 $completion = new completion_info($course); 3887 3888 // Completion should now be met. 3889 $completiondata = $completion->get_data($cm, false, $student->id); 3890 $this->assertEquals(1, $completiondata->completionstate); 3891 3892 $completiondata = $completion->get_data($cm, false, $otherstudent->id); 3893 $this->assertEquals(1, $completiondata->completionstate); 3894 } 3895 3896 /** 3897 * Data provider for test_fix_null_grades 3898 * @return array[] Test data for test_fix_null_grades. Each element should contain grade, expectedcount and gradebookvalue 3899 */ 3900 public function fix_null_grades_provider() { 3901 return [ 3902 'Negative less than one is errant' => [ 3903 'grade' => -0.64, 3904 'gradebookvalue' => null, 3905 ], 3906 'Negative more than one is errant' => [ 3907 'grade' => -30.18, 3908 'gradebookvalue' => null, 3909 ], 3910 'Negative one exactly is not errant, but shouldn\'t be pushed to gradebook' => [ 3911 'grade' => ASSIGN_GRADE_NOT_SET, 3912 'gradebookvalue' => null, 3913 ], 3914 'Positive grade is not errant' => [ 3915 'grade' => 1, 3916 'gradebookvalue' => 1, 3917 ], 3918 'Large grade is not errant' => [ 3919 'grade' => 100, 3920 'gradebookvalue' => 100, 3921 ], 3922 'Zero grade is not errant' => [ 3923 'grade' => 0, 3924 'gradebookvalue' => 0, 3925 ], 3926 ]; 3927 } 3928 3929 /** 3930 * Test fix_null_grades 3931 * @param number $grade The grade we should set in the assign grading table. 3932 * @param number $expectedcount The finalgrade we expect in the gradebook after fixing the grades. 3933 * @dataProvider fix_null_grades_provider 3934 */ 3935 public function test_fix_null_grades($grade, $gradebookvalue) { 3936 global $DB; 3937 3938 $this->resetAfterTest(); 3939 3940 $course = $this->getDataGenerator()->create_course(); 3941 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3942 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 3943 3944 $this->setUser($teacher); 3945 $assign = $this->create_instance($course); 3946 3947 // Try getting a student's grade. This will give a grade of -1. 3948 // Then we can override it with a bad negative grade. 3949 $assign->get_user_grade($student->id, true); 3950 3951 // Set the grade to something errant. 3952 // We don't set the grader here, so we expect it to be -1 as a result. 3953 $DB->set_field( 3954 'assign_grades', 3955 'grade', 3956 $grade, 3957 [ 3958 'userid' => $student->id, 3959 'assignment' => $assign->get_instance()->id, 3960 ] 3961 ); 3962 $assign->grade = $grade; 3963 $assigntemp = clone $assign->get_instance(); 3964 $assigntemp->cmidnumber = $assign->get_course_module()->idnumber; 3965 assign_update_grades($assigntemp); 3966 3967 // Check that the gradebook was updated with the assign grade. So we can guarentee test results later on. 3968 $expectedgrade = $grade == -1 ? null : $grade; // Assign sends null to the gradebook for -1 grades. 3969 $gradegrade = grade_grade::fetch(array('userid' => $student->id, 'itemid' => $assign->get_grade_item()->id)); 3970 $this->assertEquals(-1, $gradegrade->usermodified); 3971 $this->assertEquals($expectedgrade, $gradegrade->rawgrade); 3972 3973 // Call fix_null_grades(). 3974 $method = new ReflectionMethod(assign::class, 'fix_null_grades'); 3975 $method->setAccessible(true); 3976 $result = $method->invoke($assign); 3977 3978 $this->assertSame(true, $result); 3979 3980 $gradegrade = grade_grade::fetch(array('userid' => $student->id, 'itemid' => $assign->get_grade_item()->id)); 3981 3982 $this->assertEquals(-1, $gradegrade->usermodified); 3983 $this->assertEquals($gradebookvalue, $gradegrade->finalgrade); 3984 3985 // Check that the grade was updated in the gradebook by fix_null_grades. 3986 $this->assertEquals($gradebookvalue, $gradegrade->finalgrade); 3987 } 3988 3989 /** 3990 * Test grade override displays 'Graded' for students 3991 */ 3992 public function test_grade_submission_override() { 3993 global $DB, $PAGE, $OUTPUT; 3994 3995 $this->resetAfterTest(); 3996 3997 $course = $this->getDataGenerator()->create_course(); 3998 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 3999 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 4000 4001 $this->setUser($teacher); 4002 $assign = $this->create_instance($course, [ 4003 'assignsubmission_onlinetext_enabled' => 1, 4004 ]); 4005 4006 // Simulate adding a grade. 4007 $this->setUser($teacher); 4008 $data = new stdClass(); 4009 $data->grade = '50.0'; 4010 $assign->testable_apply_grade_to_user($data, $student->id, 0); 4011 4012 // Set grade override. 4013 $gradegrade = grade_grade::fetch([ 4014 'userid' => $student->id, 4015 'itemid' => $assign->get_grade_item()->id, 4016 ]); 4017 4018 // Check that grade submission is not overridden yet. 4019 $this->assertEquals(false, $gradegrade->is_overridden()); 4020 4021 // Simulate a submission. 4022 $this->setUser($student); 4023 $submission = $assign->get_user_submission($student->id, true); 4024 4025 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 4026 4027 // Set override grade grade, and check that grade submission has been overridden. 4028 $gradegrade->set_overridden(true); 4029 $this->assertEquals(true, $gradegrade->is_overridden()); 4030 4031 // Check that submissionslocked message 'This assignment is not accepting submissions' does not appear for student. 4032 $gradingtable = new assign_grading_table($assign, 1, '', 0, true); 4033 $output = $assign->get_renderer()->render($gradingtable); 4034 $this->assertContains(get_string('submissionstatus_', 'assign'), $output); 4035 4036 $assignsubmissionstatus = $assign->get_assign_submission_status_renderable($student, true); 4037 $output2 = $assign->get_renderer()->render($assignsubmissionstatus); 4038 4039 // Check that submissionslocked 'This assignment is not accepting submissions' message does not appear for student. 4040 $this->assertNotContains(get_string('submissionslocked', 'assign'), $output2); 4041 // Check that submissionstatus_marked 'Graded' message does appear for student. 4042 $this->assertContains(get_string('submissionstatus_marked', 'assign'), $output2); 4043 } 4044 4045 /** 4046 * Test the result of get_filters is consistent. 4047 */ 4048 public function test_get_filters() { 4049 $this->resetAfterTest(); 4050 4051 $course = $this->getDataGenerator()->create_course(); 4052 $assign = $this->create_instance($course); 4053 $valid = $assign->get_filters(); 4054 4055 $this->assertEquals(count($valid), 5); 4056 } 4057 4058 /** 4059 * Test assign->get_instance() for a number of cases, as defined in the data provider. 4060 * 4061 * @dataProvider assign_get_instance_provider 4062 * @param array $courseconfig the config to use when creating the course. 4063 * @param array $assignconfig the config to use when creating the assignment. 4064 * @param array $enrolconfig the config to use when enrolling the user (this will be the active user). 4065 * @param array $expectedproperties an map containing the expected names and values for the assign instance data. 4066 */ 4067 public function test_assign_get_instance(array $courseconfig, array $assignconfig, array $enrolconfig, 4068 array $expectedproperties) { 4069 $this->resetAfterTest(); 4070 4071 set_config('enablecourserelativedates', true); // Enable relative dates at site level. 4072 4073 $course = $this->getDataGenerator()->create_course($courseconfig); 4074 $assign = $this->create_instance($course, $assignconfig); 4075 $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig)); 4076 4077 $instance = $assign->get_instance($user->id); 4078 4079 foreach ($expectedproperties as $propertyname => $propertyval) { 4080 $this->assertEquals($propertyval, $instance->$propertyname); 4081 } 4082 } 4083 4084 /** 4085 * The test_assign_get_instance data provider. 4086 */ 4087 public function assign_get_instance_provider() { 4088 $timenow = time(); 4089 4090 // The get_default_instance() method shouldn't calculate any properties per-user. It should just return the record data. 4091 // We'll confirm this works for a few different user types anyway, just like we do for get_instance(). 4092 return [ 4093 'Teacher whose enrolment starts after the course start date, relative dates mode enabled' => [ 4094 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS], 4095 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4096 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual', 4097 'startdate' => $timenow - 8 * DAYSECS], 4098 'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS] 4099 ], 4100 'Teacher whose enrolment starts before the course start date, relative dates mode enabled' => [ 4101 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS], 4102 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4103 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual', 4104 'startdate' => $timenow - 12 * DAYSECS], 4105 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS] 4106 ], 4107 'Teacher whose enrolment starts after the course start date, relative dates mode disabled' => [ 4108 'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS], 4109 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4110 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual', 4111 'startdate' => $timenow - 8 * DAYSECS], 4112 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS] 4113 ], 4114 'Student whose enrolment starts after the course start date, relative dates mode enabled' => [ 4115 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS], 4116 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4117 'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual', 4118 'startdate' => $timenow - 8 * DAYSECS], 4119 'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS] 4120 ], 4121 'Student whose enrolment starts before the course start date, relative dates mode enabled' => [ 4122 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS], 4123 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4124 'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual', 4125 'startdate' => $timenow - 12 * DAYSECS], 4126 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS] 4127 ], 4128 'Student whose enrolment starts after the course start date, relative dates mode disabled' => [ 4129 'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS], 4130 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4131 'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual', 4132 'startdate' => $timenow - 8 * DAYSECS], 4133 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS] 4134 ], 4135 ]; 4136 } 4137 4138 /** 4139 * Test assign->get_default_instance() for a number of cases, as defined in the date provider. 4140 * 4141 * @dataProvider assign_get_default_instance_provider 4142 * @param array $courseconfig the config to use when creating the course. 4143 * @param array $assignconfig the config to use when creating the assignment. 4144 * @param array $enrolconfig the config to use when enrolling the user (this will be the active user). 4145 * @param array $expectedproperties an map containing the expected names and values for the assign instance data. 4146 */ 4147 public function test_assign_get_default_instance(array $courseconfig, array $assignconfig, array $enrolconfig, 4148 array $expectedproperties) { 4149 $this->resetAfterTest(); 4150 4151 set_config('enablecourserelativedates', true); // Enable relative dates at site level. 4152 4153 $course = $this->getDataGenerator()->create_course($courseconfig); 4154 $assign = $this->create_instance($course, $assignconfig); 4155 $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig)); 4156 4157 $this->setUser($user); 4158 $defaultinstance = $assign->get_default_instance(); 4159 4160 foreach ($expectedproperties as $propertyname => $propertyval) { 4161 $this->assertEquals($propertyval, $defaultinstance->$propertyname); 4162 } 4163 } 4164 4165 /** 4166 * The test_assign_get_default_instance data provider. 4167 */ 4168 public function assign_get_default_instance_provider() { 4169 $timenow = time(); 4170 4171 // The get_default_instance() method shouldn't calculate any properties per-user. It should just return the record data. 4172 // We'll confirm this works for a few different user types anyway, just like we do for get_instance(). 4173 return [ 4174 'Teacher whose enrolment starts after the course start date, relative dates mode enabled' => [ 4175 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS], 4176 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4177 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual', 4178 'startdate' => $timenow - 8 * DAYSECS], 4179 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS] 4180 ], 4181 'Teacher whose enrolment starts before the course start date, relative dates mode enabled' => [ 4182 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS], 4183 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4184 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual', 4185 'startdate' => $timenow - 12 * DAYSECS], 4186 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS] 4187 ], 4188 'Teacher whose enrolment starts after the course start date, relative dates mode disabled' => [ 4189 'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS], 4190 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4191 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual', 4192 'startdate' => $timenow - 8 * DAYSECS], 4193 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS] 4194 ], 4195 'Student whose enrolment starts after the course start date, relative dates mode enabled' => [ 4196 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS], 4197 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS], 4198 'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual', 4199 'startdate' => $timenow - 8 * DAYSECS], 4200 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS] 4201 ], 4202 ]; 4203 } 4204 4205 /** 4206 * Test showing group override duedate for admin 4207 */ 4208 public function test_view_group_override() { 4209 global $DB, $PAGE; 4210 4211 $this->resetAfterTest(); 4212 $course = $this->getDataGenerator()->create_course(); 4213 4214 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 4215 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 4216 4217 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 4218 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 4219 groups_add_member($group1, $student1); 4220 groups_add_member($group1, $teacher); 4221 4222 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 4223 groups_add_member($group2, $student2); 4224 4225 $assign = $this->create_instance($course, [ 4226 'groupmode' => 1, 4227 'duedate' => 1558999899, 4228 ]); 4229 $instance = $assign->get_instance(); 4230 4231 // Overrides for two groups. 4232 $overrides = [ 4233 (object) [ 4234 'assignid' => $instance->id, 4235 'groupid' => $group1->id, 4236 'userid' => null, 4237 'sortorder' => 1, 4238 'duedate' => 1568990258, 4239 ], 4240 (object) [ 4241 'assignid' => $instance->id, 4242 'groupid' => $group2->id, 4243 'userid' => null, 4244 'sortorder' => 2, 4245 'duedate' => 1559900258, 4246 ], 4247 ]; 4248 4249 foreach ($overrides as &$override) { 4250 $override->id = $DB->insert_record('assign_overrides', $override); 4251 } 4252 4253 $currenturl = new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)); 4254 $PAGE->set_url($currenturl); 4255 $output1 = ''; 4256 // Other users should see duedate of the assignment. 4257 $this->setUser($student2); 4258 $summary = $assign->get_assign_grading_summary_renderable($group1->id); 4259 $output1 .= $assign->get_renderer()->render($summary); 4260 $this->assertContains('Tuesday, 28 May 2019, 7:31 AM', $output1, '', true); 4261 4262 $output2 = ''; 4263 // Teacher should be able to see all group override duedate. 4264 $this->setUser($teacher); 4265 $summary = $assign->get_assign_grading_summary_renderable($group1->id); 4266 $output2 .= $assign->get_renderer()->render($summary); 4267 $this->assertContains('Friday, 20 September 2019, 10:37 PM', $output2, '', true); 4268 $summary = $assign->get_assign_grading_summary_renderable($group2->id); 4269 $output3 = ''; 4270 $output3 .= $assign->get_renderer()->render($summary); 4271 $this->assertContains('Friday, 7 June 2019, 5:37 PM', $output3, '', true); 4272 } 4273 4274 /** 4275 * Test that cron task uses task API to get its last run time. 4276 */ 4277 public function test_cron_use_task_api_to_get_lastruntime() { 4278 global $DB; 4279 $this->resetAfterTest(); 4280 $course = $this->getDataGenerator()->create_course(); 4281 4282 // Create an assignment which allows submissions from 3 days ago. 4283 $assign1 = $this->create_instance($course, [ 4284 'duedate' => time() + DAYSECS, 4285 'alwaysshowdescription' => 0, 4286 'allowsubmissionsfromdate' => time() - 3 * DAYSECS, 4287 'intro' => 'This one should not be re-created', 4288 ]); 4289 4290 // Create an assignment which allows submissions from 1 day ago. 4291 $assign2 = $this->create_instance($course, [ 4292 'duedate' => time() + DAYSECS, 4293 'alwaysshowdescription' => 0, 4294 'allowsubmissionsfromdate' => time() - DAYSECS, 4295 'intro' => 'This one should be re-created', 4296 ]); 4297 4298 // Set last run time 2 days ago. 4299 $DB->set_field('task_scheduled', 'lastruntime', time() - 2 * DAYSECS, ['classname' => '\mod_assign\task\cron_task']); 4300 4301 // Remove events to make sure cron will update calendar and re-create one of them. 4302 $params = array('modulename' => 'assign', 'instance' => $assign1->get_instance()->id); 4303 $DB->delete_records('event', $params); 4304 $params = array('modulename' => 'assign', 'instance' => $assign2->get_instance()->id); 4305 $DB->delete_records('event', $params); 4306 4307 // Run cron. 4308 assign::cron(); 4309 4310 // Assert that calendar hasn't been updated for the first assignment as it's supposed to be 4311 // updated as part of previous cron runs (allowsubmissionsfromdate is less than lastruntime). 4312 $params = array('modulename' => 'assign', 'instance' => $assign1->get_instance()->id); 4313 $event1 = $DB->get_record('event', $params); 4314 $this->assertEmpty($event1); 4315 4316 // Assert that calendar has been updated for the second assignment 4317 // because its allowsubmissionsfromdate is greater than lastruntime. 4318 $params = array('modulename' => 'assign', 'instance' => $assign2->get_instance()->id); 4319 $event2 = $DB->get_record('event', $params); 4320 $this->assertNotEmpty($event2); 4321 $this->assertSame('This one should be re-created', $event2->description); 4322 } 4323 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body