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