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