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