Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 38 and 311] [Versions 39 and 311]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Unit tests for (some of) mod/assign/locallib.php. 19 * 20 * @package mod_assign 21 * @category phpunit 22 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 namespace mod_assign; 26 27 use assign_header; 28 use mod_assign_grade_form; 29 use mod_assign_test_generator; 30 use mod_assign_testable_assign; 31 32 defined('MOODLE_INTERNAL') || die(); 33 34 global $CFG; 35 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 36 require_once($CFG->dirroot . '/mod/assign/upgradelib.php'); 37 require_once($CFG->dirroot . '/mod/assign/tests/generator.php'); 38 39 /** 40 * Unit tests for (some of) mod/assign/locallib.php. 41 * 42 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 */ 45 class locallib_test extends \advanced_testcase { 46 47 // Use the generator helper. 48 use mod_assign_test_generator; 49 50 public function test_return_links() { 51 global $PAGE; 52 53 $this->resetAfterTest(); 54 $course = $this->getDataGenerator()->create_course(); 55 56 $assign = $this->create_instance($course); 57 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 58 59 $assign->register_return_link('RETURNACTION', ['param' => 1]); 60 $this->assertEquals('RETURNACTION', $assign->get_return_action()); 61 $this->assertEquals(['param' => 1], $assign->get_return_params()); 62 } 63 64 public function test_get_feedback_plugins() { 65 $this->resetAfterTest(); 66 $course = $this->getDataGenerator()->create_course(); 67 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 68 69 $this->setUser($teacher); 70 $assign = $this->create_instance($course); 71 $installedplugins = array_keys(\core_component::get_plugin_list('assignfeedback')); 72 73 foreach ($assign->get_feedback_plugins() as $plugin) { 74 $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins'); 75 } 76 } 77 78 public function test_get_submission_plugins() { 79 $this->resetAfterTest(); 80 $course = $this->getDataGenerator()->create_course(); 81 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 82 83 $this->setUser($teacher); 84 $assign = $this->create_instance($course); 85 $installedplugins = array_keys(\core_component::get_plugin_list('assignsubmission')); 86 87 foreach ($assign->get_submission_plugins() as $plugin) { 88 $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins'); 89 } 90 } 91 92 public function test_is_blind_marking() { 93 $this->resetAfterTest(); 94 $course = $this->getDataGenerator()->create_course(); 95 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 96 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 97 98 $this->setUser($teacher); 99 $assign = $this->create_instance($course, ['blindmarking' => 1]); 100 $this->assertEquals(true, $assign->is_blind_marking()); 101 102 // Test cannot see student names. 103 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 104 $output = $assign->get_renderer()->render($gradingtable); 105 $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign'))); 106 107 // Test students cannot reveal identities. 108 $nopermission = false; 109 $student->ignoresesskey = true; 110 $this->setUser($student); 111 $this->expectException('required_capability_exception'); 112 $assign->reveal_identities(); 113 $student->ignoresesskey = false; 114 115 // Test teachers cannot reveal identities. 116 $nopermission = false; 117 $teacher->ignoresesskey = true; 118 $this->setUser($teacher); 119 $this->expectException('required_capability_exception'); 120 $assign->reveal_identities(); 121 $teacher->ignoresesskey = false; 122 123 // Test sesskey is required. 124 $this->setUser($teacher); 125 $this->expectException('moodle_exception'); 126 $assign->reveal_identities(); 127 128 // Test editingteacher can reveal identities if sesskey is ignored. 129 $teacher->ignoresesskey = true; 130 $this->setUser($teacher); 131 $assign->reveal_identities(); 132 $this->assertEquals(false, $assign->is_blind_marking()); 133 $teacher->ignoresesskey = false; 134 135 // Test student names are visible. 136 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 137 $output = $assign->get_renderer()->render($gradingtable); 138 $this->assertEquals(false, strpos($output, get_string('hiddenuser', 'assign'))); 139 140 // Set this back to default. 141 $teacher->ignoresesskey = false; 142 } 143 144 /** 145 * Data provider for test_get_assign_perpage 146 * 147 * @return array Provider data 148 */ 149 public function get_assign_perpage_provider() { 150 return array( 151 array( 152 'maxperpage' => -1, 153 'userprefs' => array( 154 -1 => -1, 155 10 => 10, 156 20 => 20, 157 50 => 50, 158 ), 159 ), 160 array( 161 'maxperpage' => 15, 162 'userprefs' => array( 163 -1 => 15, 164 10 => 10, 165 20 => 15, 166 50 => 15, 167 ), 168 ), 169 ); 170 } 171 172 /** 173 * Test maxperpage 174 * 175 * @dataProvider get_assign_perpage_provider 176 * @param integer $maxperpage site config value 177 * @param array $userprefs Array of user preferences and expected page sizes 178 */ 179 public function test_get_assign_perpage($maxperpage, $userprefs) { 180 $this->resetAfterTest(); 181 $course = $this->getDataGenerator()->create_course(); 182 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 183 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 184 185 $this->setUser($teacher); 186 $assign = $this->create_instance($course); 187 188 set_config('maxperpage', $maxperpage, 'assign'); 189 set_user_preference('assign_perpage', null); 190 $this->assertEquals(10, $assign->get_assign_perpage()); 191 foreach ($userprefs as $pref => $perpage) { 192 set_user_preference('assign_perpage', $pref); 193 $this->assertEquals($perpage, $assign->get_assign_perpage()); 194 } 195 } 196 197 /** 198 * Test filter by requires grading. 199 * 200 * This is specifically checking an assignment with no grade to make sure we do not 201 * get an exception thrown when rendering the grading table for this type of assignment. 202 */ 203 public function test_gradingtable_filter_by_requiresgrading_no_grade() { 204 global $PAGE; 205 206 $this->resetAfterTest(); 207 208 $course = $this->getDataGenerator()->create_course(); 209 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 210 $this->setUser($teacher); 211 $assign = $this->create_instance($course, [ 212 'assignsubmission_onlinetext_enabled' => 1, 213 'assignfeedback_comments_enabled' => 0, 214 'grade' => GRADE_TYPE_NONE 215 ]); 216 217 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array( 218 'id' => $assign->get_course_module()->id, 219 'action' => 'grading', 220 ))); 221 222 // Render the table with the requires grading filter. 223 $gradingtable = new \assign_grading_table($assign, 1, ASSIGN_FILTER_REQUIRE_GRADING, 0, true); 224 $output = $assign->get_renderer()->render($gradingtable); 225 226 // Test that the filter function does not throw errors for assignments with no grade. 227 $this->assertStringContainsString(get_string('nothingtodisplay'), $output); 228 } 229 230 231 /** 232 * Test submissions with extension date. 233 */ 234 public function test_gradingtable_extension_due_date() { 235 global $PAGE; 236 237 $this->resetAfterTest(); 238 $course = $this->getDataGenerator()->create_course(); 239 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 240 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 241 242 // Setup the assignment. 243 $this->setUser($teacher); 244 $time = time(); 245 $assign = $this->create_instance($course, [ 246 'assignsubmission_onlinetext_enabled' => 1, 247 'duedate' => time() - (4 * DAYSECS), 248 ]); 249 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array( 250 'id' => $assign->get_course_module()->id, 251 'action' => 'grading', 252 ))); 253 254 // Check that the assignment is late. 255 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 256 $output = $assign->get_renderer()->render($gradingtable); 257 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output); 258 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS))), $output); 259 260 // Grant an extension. 261 $extendedtime = $time + (2 * DAYSECS); 262 $assign->testable_save_user_extension($student->id, $extendedtime); 263 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 264 $output = $assign->get_renderer()->render($gradingtable); 265 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output); 266 $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output); 267 268 // Simulate a submission. 269 $this->setUser($student); 270 $submission = $assign->get_user_submission($student->id, true); 271 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 272 $assign->testable_update_submission($submission, $student->id, true, false); 273 $data = new \stdClass(); 274 $data->onlinetext_editor = [ 275 'itemid' => file_get_unused_draft_itemid(), 276 'text' => 'Submission text', 277 'format' => FORMAT_MOODLE, 278 ]; 279 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 280 $plugin->save($submission, $data); 281 282 // Verify output. 283 $this->setUser($teacher); 284 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 285 $output = $assign->get_renderer()->render($gradingtable); 286 $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output); 287 $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output); 288 } 289 290 /** 291 * Test that late submissions with extension date calculate correctly. 292 */ 293 public function test_gradingtable_extension_date_calculation_for_lateness() { 294 global $PAGE; 295 296 $this->resetAfterTest(); 297 $course = $this->getDataGenerator()->create_course(); 298 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 299 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 300 301 // Setup the assignment. 302 $this->setUser($teacher); 303 $time = time(); 304 $assign = $this->create_instance($course, [ 305 'assignsubmission_onlinetext_enabled' => 1, 306 'duedate' => time() - (4 * DAYSECS), 307 ]); 308 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array( 309 'id' => $assign->get_course_module()->id, 310 'action' => 'grading', 311 ))); 312 313 // Check that the assignment is late. 314 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 315 $output = $assign->get_renderer()->render($gradingtable); 316 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output); 317 $difftime = time() - $time; 318 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output); 319 320 // Grant an extension that is in the past. 321 $assign->testable_save_user_extension($student->id, $time - (2 * DAYSECS)); 322 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 323 $output = $assign->get_renderer()->render($gradingtable); 324 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output); 325 $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output); 326 $difftime = time() - $time; 327 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output); 328 329 // Simulate a submission. 330 $this->setUser($student); 331 $submission = $assign->get_user_submission($student->id, true); 332 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 333 $assign->testable_update_submission($submission, $student->id, true, false); 334 $data = new \stdClass(); 335 $data->onlinetext_editor = [ 336 'itemid' => file_get_unused_draft_itemid(), 337 'text' => 'Submission text', 338 'format' => FORMAT_MOODLE, 339 ]; 340 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 341 $plugin->save($submission, $data); 342 $submittedtime = time(); 343 344 // Verify output. 345 $this->setUser($teacher); 346 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 347 $output = $assign->get_renderer()->render($gradingtable); 348 $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output); 349 $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output); 350 351 $difftime = $submittedtime - $time; 352 $this->assertStringContainsString(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)), 353 $output); 354 } 355 356 public function test_gradingtable_status_rendering() { 357 global $PAGE; 358 359 $this->resetAfterTest(); 360 $course = $this->getDataGenerator()->create_course(); 361 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 362 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 363 364 // Setup the assignment. 365 $this->setUser($teacher); 366 $time = time(); 367 $assign = $this->create_instance($course, [ 368 'assignsubmission_onlinetext_enabled' => 1, 369 'duedate' => $time - (4 * DAYSECS), 370 ]); 371 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array( 372 'id' => $assign->get_course_module()->id, 373 'action' => 'grading', 374 ))); 375 376 // Check that the assignment is late. 377 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 378 $output = $assign->get_renderer()->render($gradingtable); 379 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output); 380 $difftime = time() - $time; 381 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output); 382 383 // Simulate a student viewing the assignment without submitting. 384 $this->setUser($student); 385 $submission = $assign->get_user_submission($student->id, true); 386 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW; 387 $assign->testable_update_submission($submission, $student->id, true, false); 388 $submittedtime = time(); 389 390 // Verify output. 391 $this->setUser($teacher); 392 $gradingtable = new \assign_grading_table($assign, 1, '', 0, true); 393 $output = $assign->get_renderer()->render($gradingtable); 394 $difftime = $submittedtime - $time; 395 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output); 396 397 $document = new \DOMDocument(); 398 @$document->loadHTML($output); 399 $xpath = new \DOMXPath($document); 400 $this->assertEmpty($xpath->evaluate('string(//td[@id="mod_assign_grading-' . $assign->get_context()->id. '_r0_c8"])')); 401 } 402 403 /** 404 * Check that group submission information is rendered correctly in the 405 * grading table. 406 */ 407 public function test_gradingtable_group_submissions_rendering() { 408 global $PAGE; 409 410 $this->resetAfterTest(); 411 $course = $this->getDataGenerator()->create_course(); 412 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 413 414 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 415 groups_add_member($group, $teacher); 416 417 $students = []; 418 419 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 420 $students[] = $student; 421 groups_add_member($group, $student); 422 423 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 424 $students[] = $student; 425 groups_add_member($group, $student); 426 427 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 428 $students[] = $student; 429 groups_add_member($group, $student); 430 431 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 432 $students[] = $student; 433 groups_add_member($group, $student); 434 435 // Verify group assignments. 436 $this->setUser($teacher); 437 $assign = $this->create_instance($course, [ 438 'teamsubmission' => 1, 439 'assignsubmission_onlinetext_enabled' => 1, 440 'submissiondrafts' => 1, 441 'requireallteammemberssubmit' => 0, 442 ]); 443 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array( 444 'id' => $assign->get_course_module()->id, 445 'action' => 'grading', 446 ))); 447 448 // Add a submission. 449 $this->setUser($student); 450 $data = new \stdClass(); 451 $data->onlinetext_editor = [ 452 'itemid' => file_get_unused_draft_itemid(), 453 'text' => 'Submission text', 454 'format' => FORMAT_MOODLE, 455 ]; 456 $notices = array(); 457 $assign->save_submission($data, $notices); 458 459 $submission = $assign->get_group_submission($student->id, 0, true); 460 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 461 $assign->testable_update_submission($submission, $student->id, true, true); 462 463 // Check output. 464 $this->setUser($teacher); 465 $gradingtable = new \assign_grading_table($assign, 4, '', 0, true); 466 $output = $assign->get_renderer()->render($gradingtable); 467 $document = new \DOMDocument(); 468 @$document->loadHTML($output); 469 $xpath = new \DOMXPath($document); 470 471 // The XPath expression is based on the unique ID of the table. 472 $xpathuniqueidroot = 'mod_assign_grading-' . $assign->get_context()->id; 473 474 // Check status. 475 $this->assertSame(get_string('submissionstatus_submitted', 'assign'), 476 $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c4"]/div[@class="submissionstatussubmitted"])')); 477 $this->assertSame(get_string('submissionstatus_submitted', 'assign'), 478 $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c4"]/div[@class="submissionstatussubmitted"])')); 479 480 // Check submission last modified date. 481 $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c8"])'))); 482 $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c8"])'))); 483 484 // Check group. 485 $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c5"])')); 486 $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c5"])')); 487 488 // Check submission text. 489 $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c9"]/div/div)')); 490 $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c9"]/div/div)')); 491 492 // Check comments can be made. 493 $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r0_c10"]//textarea)')); 494 $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r3_c10"]//textarea)')); 495 } 496 497 public function test_show_intro() { 498 $this->resetAfterTest(); 499 $course = $this->getDataGenerator()->create_course(); 500 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 501 502 // Test whether we are showing the intro at the correct times. 503 $this->setUser($teacher); 504 $assign = $this->create_instance($course, ['alwaysshowdescription' => 1]); 505 506 $this->assertEquals(true, $assign->testable_show_intro()); 507 508 $tomorrow = time() + DAYSECS; 509 510 $assign = $this->create_instance($course, [ 511 'alwaysshowdescription' => 0, 512 'allowsubmissionsfromdate' => $tomorrow, 513 ]); 514 $this->assertEquals(false, $assign->testable_show_intro()); 515 $yesterday = time() - DAYSECS; 516 $assign = $this->create_instance($course, [ 517 'alwaysshowdescription' => 0, 518 'allowsubmissionsfromdate' => $yesterday, 519 ]); 520 $this->assertEquals(true, $assign->testable_show_intro()); 521 } 522 523 public function test_has_submissions_or_grades() { 524 $this->resetAfterTest(); 525 $course = $this->getDataGenerator()->create_course(); 526 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 527 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 528 529 $this->setUser($teacher); 530 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]); 531 $instance = $assign->get_instance(); 532 533 // Should start empty. 534 $this->assertEquals(false, $assign->has_submissions_or_grades()); 535 536 // Simulate a submission. 537 $this->setUser($student); 538 $submission = $assign->get_user_submission($student->id, true); 539 540 // The submission is still new. 541 $this->assertEquals(false, $assign->has_submissions_or_grades()); 542 543 // Submit the submission. 544 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 545 $assign->testable_update_submission($submission, $student->id, true, false); 546 $data = new \stdClass(); 547 $data->onlinetext_editor = array( 548 'itemid' => file_get_unused_draft_itemid(), 549 'text' => 'Submission text', 550 'format' => FORMAT_MOODLE); 551 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 552 $plugin->save($submission, $data); 553 554 // Now test again. 555 $this->assertEquals(true, $assign->has_submissions_or_grades()); 556 } 557 558 public function test_delete_grades() { 559 $this->resetAfterTest(); 560 $course = $this->getDataGenerator()->create_course(); 561 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 562 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 563 564 $this->setUser($teacher); 565 $assign = $this->create_instance($course); 566 567 // Simulate adding a grade. 568 $this->setUser($teacher); 569 $data = new \stdClass(); 570 $data->grade = '50.0'; 571 $assign->testable_apply_grade_to_user($data, $student->id, 0); 572 573 // Now see if the data is in the gradebook. 574 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id); 575 576 $this->assertNotEquals(0, count($gradinginfo->items)); 577 578 $assign->testable_delete_grades(); 579 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id); 580 581 $this->assertEquals(0, count($gradinginfo->items)); 582 } 583 584 public function test_delete_instance() { 585 $this->resetAfterTest(); 586 $course = $this->getDataGenerator()->create_course(); 587 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 588 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 589 590 $this->setUser($teacher); 591 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]); 592 593 // Simulate adding a grade. 594 $this->setUser($teacher); 595 $data = new \stdClass(); 596 $data->grade = '50.0'; 597 $assign->testable_apply_grade_to_user($data, $student->id, 0); 598 599 // Simulate a submission. 600 $this->add_submission($student, $assign); 601 602 // Now try and delete. 603 $this->setUser($teacher); 604 $this->assertEquals(true, $assign->delete_instance()); 605 } 606 607 public function test_reset_userdata() { 608 global $DB; 609 610 $this->resetAfterTest(); 611 $course = $this->getDataGenerator()->create_course(); 612 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 613 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 614 615 $now = time(); 616 $this->setUser($teacher); 617 $assign = $this->create_instance($course, [ 618 'assignsubmission_onlinetext_enabled' => 1, 619 'duedate' => $now, 620 ]); 621 622 // Simulate adding a grade. 623 $this->add_submission($student, $assign); 624 $this->submit_for_grading($student, $assign); 625 $this->mark_submission($teacher, $assign, $student, 50.0); 626 627 // Simulate a submission. 628 $this->setUser($student); 629 $submission = $assign->get_user_submission($student->id, true); 630 $data = new \stdClass(); 631 $data->onlinetext_editor = array( 632 'itemid' => file_get_unused_draft_itemid(), 633 'text' => 'Submission text', 634 'format' => FORMAT_MOODLE); 635 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 636 $plugin->save($submission, $data); 637 638 $this->assertEquals(true, $assign->has_submissions_or_grades()); 639 // Now try and reset. 640 $data = new \stdClass(); 641 $data->reset_assign_submissions = 1; 642 $data->reset_gradebook_grades = 1; 643 $data->reset_assign_user_overrides = 1; 644 $data->reset_assign_group_overrides = 1; 645 $data->courseid = $course->id; 646 $data->timeshift = DAYSECS; 647 $this->setUser($teacher); 648 $assign->reset_userdata($data); 649 $this->assertEquals(false, $assign->has_submissions_or_grades()); 650 651 // Reload the instance data. 652 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id)); 653 $this->assertEquals($now + DAYSECS, $instance->duedate); 654 655 // Test reset using assign_reset_userdata(). 656 $assignduedate = $instance->duedate; // Keep old updated value for comparison. 657 $data->timeshift = (2 * DAYSECS); 658 assign_reset_userdata($data); 659 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id)); 660 $this->assertEquals($assignduedate + (2 * DAYSECS), $instance->duedate); 661 662 // Create one more assignment and reset, make sure time shifted for previous assignment is not changed. 663 $assign2 = $this->create_instance($course, [ 664 'assignsubmission_onlinetext_enabled' => 1, 665 'duedate' => $now, 666 ]); 667 $assignduedate = $instance->duedate; 668 $data->timeshift = 3 * DAYSECS; 669 $assign2->reset_userdata($data); 670 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id)); 671 $this->assertEquals($assignduedate, $instance->duedate); 672 $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id)); 673 $this->assertEquals($now + 3 * DAYSECS, $instance2->duedate); 674 675 // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date. 676 $assignduedate = $instance->duedate; 677 $assign2duedate = $instance2->duedate; 678 $data->timeshift = (4 * DAYSECS); 679 assign_reset_userdata($data); 680 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id)); 681 $this->assertEquals($assignduedate + (4 * DAYSECS), $instance->duedate); 682 $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id)); 683 $this->assertEquals($assign2duedate + (4 * DAYSECS), $instance2->duedate); 684 } 685 686 public function test_plugin_settings() { 687 global $DB; 688 689 $this->resetAfterTest(); 690 691 $course = $this->getDataGenerator()->create_course(); 692 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 693 694 $now = time(); 695 $this->setUser($teacher); 696 $assign = $this->create_instance($course, [ 697 'assignsubmission_file_enabled' => 1, 698 'assignsubmission_file_maxfiles' => 12, 699 'assignsubmission_file_maxsizebytes' => 10, 700 ]); 701 702 $plugin = $assign->get_submission_plugin_by_type('file'); 703 $this->assertEquals('12', $plugin->get_config('maxfilesubmissions')); 704 } 705 706 public function test_update_calendar() { 707 global $DB; 708 709 $this->resetAfterTest(); 710 711 $course = $this->getDataGenerator()->create_course(); 712 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 713 714 $this->setUser($teacher); 715 $userctx = \context_user::instance($teacher->id)->id; 716 717 // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey. 718 $draftid = file_get_unused_draft_itemid(); 719 $_REQUEST['introeditor'] = $draftid; 720 $_POST['introeditor'] = $draftid; 721 $_POST['sesskey'] = sesskey(); 722 723 // Write links to a draft area. 724 $fakearealink1 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">link</a>', 'draftfile.php', $userctx, 725 'user', 'draft', $draftid); 726 $fakearealink2 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">new</a>', 'draftfile.php', $userctx, 727 'user', 'draft', $draftid); 728 729 // Create a new \assignment with links to a draft area. 730 $now = time(); 731 $assign = $this->create_instance($course, [ 732 'duedate' => $now, 733 'intro' => $fakearealink1, 734 'introformat' => FORMAT_HTML 735 ]); 736 737 // See if there is an event in the calendar. 738 $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id); 739 $event = $DB->get_record('event', $params); 740 $this->assertNotEmpty($event); 741 $this->assertSame('link', $event->description); // The pluginfile links are removed. 742 743 // Make sure the same works when updating the assignment. 744 $instance = $assign->get_instance(); 745 $instance->instance = $instance->id; 746 $instance->intro = $fakearealink2; 747 $instance->introformat = FORMAT_HTML; 748 $assign->update_instance($instance); 749 $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id); 750 $event = $DB->get_record('event', $params); 751 $this->assertNotEmpty($event); 752 $this->assertSame('new', $event->description); // The pluginfile links are removed. 753 754 // Create an assignment with a description that should be hidden. 755 $assign = $this->create_instance($course, [ 756 'duedate' => $now + 160, 757 'alwaysshowdescription' => false, 758 'allowsubmissionsfromdate' => $now + 60, 759 'intro' => 'Some text', 760 ]); 761 762 // Get the event from the calendar. 763 $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id); 764 $event = $DB->get_record('event', [ 765 'modulename' => 'assign', 766 'instance' => $assign->get_instance()->id, 767 ]); 768 769 $this->assertEmpty($event->description); 770 771 // Change the allowsubmissionfromdate to the past - do this directly in the DB 772 // because if we call the assignment update method - it will update the calendar 773 // and we want to test that this works from cron. 774 $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id' => $assign->get_instance()->id)); 775 // Run cron to update the event in the calendar. 776 \assign::cron(); 777 $event = $DB->get_record('event', $params); 778 779 $this->assertStringContainsString('Some text', $event->description); 780 781 } 782 783 public function test_update_instance() { 784 global $DB; 785 786 $this->resetAfterTest(); 787 788 $course = $this->getDataGenerator()->create_course(); 789 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 790 791 $this->setUser($teacher); 792 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]); 793 794 $now = time(); 795 $instance = $assign->get_instance(); 796 $instance->duedate = $now; 797 $instance->instance = $instance->id; 798 $instance->assignsubmission_onlinetext_enabled = 1; 799 800 $assign->update_instance($instance); 801 802 $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]); 803 $this->assertEquals($now, $instance->duedate); 804 } 805 806 public function test_cannot_submit_empty() { 807 global $PAGE; 808 809 $this->resetAfterTest(); 810 811 $course = $this->getDataGenerator()->create_course(); 812 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 813 814 $assign = $this->create_instance($course, ['submissiondrafts' => 1]); 815 816 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 817 818 // Test you cannot see the submit button for an offline assignment regardless. 819 $this->setUser($student); 820 $output = $assign->view_student_summary($student, true); 821 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), 822 $output, 'Can submit empty offline assignment'); 823 } 824 825 public function test_cannot_submit_empty_no_submission() { 826 global $PAGE; 827 828 $this->resetAfterTest(); 829 830 $course = $this->getDataGenerator()->create_course(); 831 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 832 833 $assign = $this->create_instance($course, [ 834 'submissiondrafts' => 1, 835 'assignsubmission_onlinetext_enabled' => 1, 836 ]); 837 838 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 839 840 // Test you cannot see the submit button for an online text assignment with no submission. 841 $this->setUser($student); 842 $output = $assign->view_student_summary($student, true); 843 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), 844 $output, 'Cannot submit empty onlinetext assignment'); 845 } 846 847 public function test_can_submit_with_submission() { 848 global $PAGE; 849 850 $this->resetAfterTest(); 851 852 $course = $this->getDataGenerator()->create_course(); 853 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 854 855 $assign = $this->create_instance($course, [ 856 'submissiondrafts' => 1, 857 'assignsubmission_onlinetext_enabled' => 1, 858 ]); 859 860 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 861 862 // Add a draft. 863 $this->add_submission($student, $assign); 864 865 // Test you can see the submit button for an online text assignment with a submission. 866 $this->setUser($student); 867 $output = $assign->view_student_summary($student, true); 868 $this->assertStringContainsString(get_string('submitassignment', 'assign'), 869 $output, 'Can submit non empty onlinetext assignment'); 870 } 871 872 /** 873 * Test new_submission_empty 874 * 875 * We only test combinations of plugins here. Individual plugins are tested 876 * in their respective test files. 877 * 878 * @dataProvider test_new_submission_empty_testcases 879 * @param string $data The file submission data 880 * @param bool $expected The expected return value 881 */ 882 public function test_new_submission_empty($data, $expected) { 883 $this->resetAfterTest(); 884 885 $course = $this->getDataGenerator()->create_course(); 886 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 887 888 $assign = $this->create_instance($course, [ 889 'assignsubmission_file_enabled' => 1, 890 'assignsubmission_file_maxfiles' => 12, 891 'assignsubmission_file_maxsizebytes' => 10, 892 'assignsubmission_onlinetext_enabled' => 1, 893 ]); 894 $this->setUser($student); 895 $submission = new \stdClass(); 896 897 if ($data['file'] && isset($data['file']['filename'])) { 898 $itemid = file_get_unused_draft_itemid(); 899 $submission->files_filemanager = $itemid; 900 $data['file'] += ['contextid' => \context_user::instance($student->id)->id, 'itemid' => $itemid]; 901 $fs = get_file_storage(); 902 $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']); 903 } 904 905 if ($data['onlinetext']) { 906 $submission->onlinetext_editor = ['text' => $data['onlinetext']]; 907 } 908 909 $result = $assign->new_submission_empty($submission); 910 $this->assertTrue($result === $expected); 911 } 912 913 /** 914 * Dataprovider for the test_new_submission_empty testcase 915 * 916 * @return array of testcases 917 */ 918 public function test_new_submission_empty_testcases() { 919 return [ 920 'With file and onlinetext' => [ 921 [ 922 'file' => [ 923 'component' => 'user', 924 'filearea' => 'draft', 925 'filepath' => '/', 926 'filename' => 'not_a_virus.exe' 927 ], 928 'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu' 929 ], 930 false 931 ] 932 ]; 933 } 934 935 public function test_list_participants() { 936 global $CFG; 937 938 $this->resetAfterTest(); 939 940 $course = $this->getDataGenerator()->create_course(); 941 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 942 943 // Create 10 students. 944 for ($i = 0; $i < 10; $i++) { 945 $this->getDataGenerator()->create_and_enrol($course, 'student'); 946 } 947 948 $this->setUser($teacher); 949 $assign = $this->create_instance($course, ['grade' => 100]); 950 951 $this->assertCount(10, $assign->list_participants(null, true)); 952 } 953 954 public function test_list_participants_activeenrol() { 955 global $CFG, $DB; 956 957 $this->resetAfterTest(); 958 959 $course = $this->getDataGenerator()->create_course(); 960 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 961 962 // Create 10 students. 963 for ($i = 0; $i < 10; $i++) { 964 $this->getDataGenerator()->create_and_enrol($course, 'student'); 965 } 966 967 // Create 10 suspended students. 968 for ($i = 0; $i < 10; $i++) { 969 $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 970 } 971 972 $this->setUser($teacher); 973 set_user_preference('grade_report_showonlyactiveenrol', false); 974 $assign = $this->create_instance($course, ['grade' => 100]); 975 976 $this->assertCount(10, $assign->list_participants(null, true)); 977 } 978 979 public function test_list_participants_with_group_restriction() { 980 global $CFG; 981 982 $this->resetAfterTest(); 983 984 $course = $this->getDataGenerator()->create_course(); 985 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 986 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 987 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 988 $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 989 990 // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group. 991 $CFG->enableavailability = true; 992 993 $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 994 $assign = $this->create_instance($course, [ 995 'grade' => 100, 996 'availability' => json_encode( 997 \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)]) 998 ), 999 ]); 1000 1001 groups_add_member($specialgroup, $student); 1002 groups_add_member($specialgroup, $otherstudent); 1003 $this->assertEquals(2, count($assign->list_participants(null, true))); 1004 } 1005 1006 public function test_get_participant_user_not_exist() { 1007 $this->resetAfterTest(); 1008 $course = $this->getDataGenerator()->create_course(); 1009 1010 $assign = $this->create_instance($course); 1011 $this->assertNull($assign->get_participant('-1')); 1012 } 1013 1014 public function test_get_participant_not_enrolled() { 1015 $this->resetAfterTest(); 1016 $course = $this->getDataGenerator()->create_course(); 1017 $assign = $this->create_instance($course); 1018 1019 $user = $this->getDataGenerator()->create_user(); 1020 $this->assertNull($assign->get_participant($user->id)); 1021 } 1022 1023 public function test_get_participant_no_submission() { 1024 $this->resetAfterTest(); 1025 $course = $this->getDataGenerator()->create_course(); 1026 $assign = $this->create_instance($course); 1027 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1028 1029 $participant = $assign->get_participant($student->id); 1030 1031 $this->assertEquals($student->id, $participant->id); 1032 $this->assertFalse($participant->submitted); 1033 $this->assertFalse($participant->requiregrading); 1034 $this->assertFalse($participant->grantedextension); 1035 } 1036 1037 public function test_get_participant_granted_extension() { 1038 $this->resetAfterTest(); 1039 $course = $this->getDataGenerator()->create_course(); 1040 $assign = $this->create_instance($course); 1041 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1042 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1043 1044 $this->setUser($teacher); 1045 $assign->save_user_extension($student->id, time()); 1046 $participant = $assign->get_participant($student->id); 1047 1048 $this->assertEquals($student->id, $participant->id); 1049 $this->assertFalse($participant->submitted); 1050 $this->assertFalse($participant->requiregrading); 1051 $this->assertTrue($participant->grantedextension); 1052 } 1053 1054 public function test_get_participant_with_ungraded_submission() { 1055 $this->resetAfterTest(); 1056 $course = $this->getDataGenerator()->create_course(); 1057 $assign = $this->create_instance($course); 1058 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1059 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1060 1061 // Simulate a submission. 1062 $this->add_submission($student, $assign); 1063 $this->submit_for_grading($student, $assign); 1064 1065 $participant = $assign->get_participant($student->id); 1066 1067 $this->assertEquals($student->id, $participant->id); 1068 $this->assertTrue($participant->submitted); 1069 $this->assertTrue($participant->requiregrading); 1070 $this->assertFalse($participant->grantedextension); 1071 } 1072 1073 /** 1074 * Tests that if a student with no submission who can no longer submit is not a participant. 1075 */ 1076 public function test_get_participant_with_no_submission_no_capability() { 1077 global $DB; 1078 $this->resetAfterTest(); 1079 $course = self::getDataGenerator()->create_course(); 1080 $coursecontext = \context_course::instance($course->id); 1081 $assign = $this->create_instance($course); 1082 $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher'); 1083 $student = self::getDataGenerator()->create_and_enrol($course, 'student'); 1084 1085 // Remove the students capability to submit. 1086 $role = $DB->get_field('role', 'id', ['shortname' => 'student']); 1087 assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext); 1088 1089 $participant = $assign->get_participant($student->id); 1090 1091 self::assertNull($participant); 1092 } 1093 1094 /** 1095 * Tests that if a student that has submitted but can no longer submit is a participant. 1096 */ 1097 public function test_get_participant_with_submission_no_capability() { 1098 global $DB; 1099 $this->resetAfterTest(); 1100 $course = self::getDataGenerator()->create_course(); 1101 $coursecontext = \context_course::instance($course->id); 1102 $assign = $this->create_instance($course); 1103 $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher'); 1104 $student = self::getDataGenerator()->create_and_enrol($course, 'student'); 1105 1106 // Simulate a submission. 1107 $this->add_submission($student, $assign); 1108 $this->submit_for_grading($student, $assign); 1109 1110 // Remove the students capability to submit. 1111 $role = $DB->get_field('role', 'id', ['shortname' => 'student']); 1112 assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext); 1113 1114 $participant = $assign->get_participant($student->id); 1115 1116 self::assertNotNull($participant); 1117 self::assertEquals($student->id, $participant->id); 1118 self::assertTrue($participant->submitted); 1119 self::assertTrue($participant->requiregrading); 1120 self::assertFalse($participant->grantedextension); 1121 } 1122 1123 public function test_get_participant_with_graded_submission() { 1124 $this->resetAfterTest(); 1125 $course = $this->getDataGenerator()->create_course(); 1126 $assign = $this->create_instance($course); 1127 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1128 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1129 1130 // Simulate a submission. 1131 $this->add_submission($student, $assign); 1132 $this->submit_for_grading($student, $assign); 1133 1134 $this->mark_submission($teacher, $assign, $student, 50.0); 1135 1136 $data = new \stdClass(); 1137 $data->grade = '50.0'; 1138 $assign->testable_apply_grade_to_user($data, $student->id, 0); 1139 1140 $participant = $assign->get_participant($student->id); 1141 1142 $this->assertEquals($student->id, $participant->id); 1143 $this->assertTrue($participant->submitted); 1144 $this->assertFalse($participant->requiregrading); 1145 $this->assertFalse($participant->grantedextension); 1146 } 1147 1148 /** 1149 * No active group and non-group submissions disallowed => 2 groups. 1150 */ 1151 public function test_count_teams_no_active_non_group_allowed() { 1152 $this->resetAfterTest(); 1153 1154 $course = $this->getDataGenerator()->create_course(); 1155 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1156 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1157 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1158 1159 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 1160 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1161 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1162 groups_add_member($group1, $student1); 1163 groups_add_member($group2, $student2); 1164 1165 $this->setUser($teacher); 1166 $assign = $this->create_instance($course, ['teamsubmission' => 1]); 1167 1168 $this->assertEquals(2, $assign->count_teams()); 1169 } 1170 1171 /** 1172 * No active group and non group submissions allowed => 2 groups + the default one. 1173 */ 1174 public function test_count_teams_non_group_allowed() { 1175 $this->resetAfterTest(); 1176 1177 $course = $this->getDataGenerator()->create_course(); 1178 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1179 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1180 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1181 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1182 1183 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 1184 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1185 1186 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 1187 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1188 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id)); 1189 1190 groups_add_member($group1, $student1); 1191 groups_add_member($group2, $student2); 1192 1193 $assign = $this->create_instance($course, [ 1194 'teamsubmission' => 1, 1195 'teamsubmissiongroupingid' => $grouping->id, 1196 'preventsubmissionnotingroup' => false, 1197 ]); 1198 1199 $this->setUser($teacher); 1200 $this->assertEquals(3, $assign->count_teams()); 1201 1202 // Active group only. 1203 $this->assertEquals(1, $assign->count_teams($group1->id)); 1204 $this->assertEquals(1, $assign->count_teams($group2->id)); 1205 } 1206 1207 /** 1208 * Active group => just selected one. 1209 */ 1210 public function test_count_teams_no_active_group() { 1211 $this->resetAfterTest(); 1212 1213 $course = $this->getDataGenerator()->create_course(); 1214 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1215 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1216 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1217 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1218 1219 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 1220 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1221 1222 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 1223 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1224 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id)); 1225 1226 groups_add_member($group1, $student1); 1227 groups_add_member($group2, $student2); 1228 1229 $assign = $this->create_instance($course, [ 1230 'teamsubmission' => 1, 1231 'preventsubmissionnotingroup' => true, 1232 ]); 1233 1234 $this->setUser($teacher); 1235 $this->assertEquals(2, $assign->count_teams()); 1236 1237 // Active group only. 1238 $this->assertEquals(1, $assign->count_teams($group1->id)); 1239 $this->assertEquals(1, $assign->count_teams($group2->id)); 1240 } 1241 1242 /** 1243 * Active group => just selected one. 1244 */ 1245 public function test_count_teams_groups_only() { 1246 $this->resetAfterTest(); 1247 1248 $course = $this->getDataGenerator()->create_course(); 1249 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 1250 1251 $assign = $this->create_instance($course, [ 1252 'teamsubmission' => 1, 1253 'teamsubmissiongroupingid' => $grouping->id, 1254 'preventsubmissionnotingroup' => false, 1255 ]); 1256 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1257 1258 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1259 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1260 groups_add_member($group1, $student1); 1261 1262 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1263 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1264 groups_add_member($group2, $student2); 1265 1266 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 1267 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id)); 1268 1269 $this->setUser($teacher); 1270 1271 $assign = $this->create_instance($course, [ 1272 'teamsubmission' => 1, 1273 'preventsubmissionnotingroup' => true, 1274 ]); 1275 $this->assertEquals(2, $assign->count_teams()); 1276 } 1277 1278 public function test_submit_to_default_group() { 1279 global $DB, $SESSION; 1280 1281 $this->resetAfterTest(); 1282 1283 $course = $this->getDataGenerator()->create_course(); 1284 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1285 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1286 1287 $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]); 1288 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1289 1290 $assign = $this->create_instance($course, [ 1291 'teamsubmission' => 1, 1292 'assignsubmission_onlinetext_enabled' => 1, 1293 'submissiondrafts' => 0, 1294 'groupmode' => VISIBLEGROUPS, 1295 ]); 1296 1297 $usergroup = $assign->get_submission_group($student->id); 1298 $this->assertFalse($usergroup, 'New student is in default group'); 1299 1300 // Add a submission. 1301 $this->add_submission($student, $assign); 1302 $this->submit_for_grading($student, $assign); 1303 1304 // Set active groups to all groups. 1305 $this->setUser($teacher); 1306 $SESSION->activegroup[$course->id]['aag'][0] = 0; 1307 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1308 1309 // Set an active group. 1310 $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id; 1311 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1312 } 1313 1314 public function test_count_submissions_no_draft() { 1315 $this->resetAfterTest(); 1316 1317 $course = $this->getDataGenerator()->create_course(); 1318 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1319 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1320 1321 $assign = $this->create_instance($course, [ 1322 'assignsubmission_onlinetext_enabled' => 1, 1323 ]); 1324 1325 $assign->get_user_submission($student->id, true); 1326 1327 // Note: Drafts count as a submission. 1328 $this->assertEquals(0, $assign->count_grades()); 1329 $this->assertEquals(0, $assign->count_submissions()); 1330 $this->assertEquals(1, $assign->count_submissions(true)); 1331 $this->assertEquals(0, $assign->count_submissions_need_grading()); 1332 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW)); 1333 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1334 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1335 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED)); 1336 } 1337 1338 public function test_count_submissions_draft() { 1339 $this->resetAfterTest(); 1340 1341 $course = $this->getDataGenerator()->create_course(); 1342 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1343 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1344 1345 $assign = $this->create_instance($course, [ 1346 'assignsubmission_onlinetext_enabled' => 1, 1347 ]); 1348 1349 $this->add_submission($student, $assign); 1350 1351 // Note: Drafts count as a submission. 1352 $this->assertEquals(0, $assign->count_grades()); 1353 $this->assertEquals(1, $assign->count_submissions()); 1354 $this->assertEquals(1, $assign->count_submissions(true)); 1355 $this->assertEquals(0, $assign->count_submissions_need_grading()); 1356 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW)); 1357 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1358 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1359 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED)); 1360 } 1361 1362 public function test_count_submissions_submitted() { 1363 global $SESSION; 1364 1365 $this->resetAfterTest(); 1366 1367 $course = $this->getDataGenerator()->create_course(); 1368 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1369 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1370 1371 $assign = $this->create_instance($course, [ 1372 'assignsubmission_onlinetext_enabled' => 1, 1373 ]); 1374 1375 $this->add_submission($student, $assign); 1376 $this->submit_for_grading($student, $assign); 1377 1378 $this->assertEquals(0, $assign->count_grades()); 1379 $this->assertEquals(1, $assign->count_submissions()); 1380 $this->assertEquals(1, $assign->count_submissions(true)); 1381 $this->assertEquals(1, $assign->count_submissions_need_grading()); 1382 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW)); 1383 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1384 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1385 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED)); 1386 } 1387 1388 public function test_count_submissions_graded() { 1389 $this->resetAfterTest(); 1390 1391 $course = $this->getDataGenerator()->create_course(); 1392 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1393 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1394 1395 $assign = $this->create_instance($course, [ 1396 'assignsubmission_onlinetext_enabled' => 1, 1397 ]); 1398 1399 $this->add_submission($student, $assign); 1400 $this->submit_for_grading($student, $assign); 1401 $this->mark_submission($teacher, $assign, $student, 50.0); 1402 1403 // Although it has been graded, it is still marked as submitted. 1404 $this->assertEquals(1, $assign->count_grades()); 1405 $this->assertEquals(1, $assign->count_submissions()); 1406 $this->assertEquals(1, $assign->count_submissions(true)); 1407 $this->assertEquals(0, $assign->count_submissions_need_grading()); 1408 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW)); 1409 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1410 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1411 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED)); 1412 } 1413 1414 public function test_count_submissions_graded_group() { 1415 global $SESSION; 1416 1417 $this->resetAfterTest(); 1418 1419 $course = $this->getDataGenerator()->create_course(); 1420 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1421 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1422 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1423 $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1424 groups_add_member($group, $student); 1425 1426 $assign = $this->create_instance($course, [ 1427 'assignsubmission_onlinetext_enabled' => 1, 1428 'groupmode' => VISIBLEGROUPS, 1429 ]); 1430 1431 $this->add_submission($student, $assign); 1432 $this->submit_for_grading($student, $assign); 1433 1434 // The user should still be listed when fetching all groups. 1435 $this->setUser($teacher); 1436 $SESSION->activegroup[$course->id]['aag'][0] = 0; 1437 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1438 1439 // The user should still be listed when fetching just their group. 1440 $SESSION->activegroup[$course->id]['aag'][0] = $group->id; 1441 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1442 1443 // The user should still be listed when fetching just their group. 1444 $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id; 1445 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1446 } 1447 1448 // TODO 1449 public function x_test_count_submissions_for_team() { 1450 $this->resetAfterTest(); 1451 1452 $course = $this->getDataGenerator()->create_course(); 1453 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1454 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1455 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1456 $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1457 groups_add_member($group, $student); 1458 1459 $assign = $this->create_instance($course, [ 1460 'assignsubmission_onlinetext_enabled' => 1, 1461 'teamsubmission' => 1, 1462 ]); 1463 1464 // Add a graded submission. 1465 $this->add_submission($student, $assign); 1466 1467 // Simulate adding a grade. 1468 $this->setUser($teacher); 1469 $data = new \stdClass(); 1470 $data->grade = '50.0'; 1471 $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0); 1472 1473 // Simulate a submission. 1474 $this->setUser($this->extrastudents[1]); 1475 $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true); 1476 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 1477 $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false); 1478 $data = new \stdClass(); 1479 $data->onlinetext_editor = array( 1480 'itemid' => file_get_unused_draft_itemid(), 1481 'text' => 'Submission text', 1482 'format' => FORMAT_MOODLE); 1483 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 1484 $plugin->save($submission, $data); 1485 1486 // Simulate a submission. 1487 $this->setUser($this->extrastudents[2]); 1488 $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true); 1489 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 1490 $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false); 1491 $data = new \stdClass(); 1492 $data->onlinetext_editor = array( 1493 'itemid' => file_get_unused_draft_itemid(), 1494 'text' => 'Submission text', 1495 'format' => FORMAT_MOODLE); 1496 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 1497 $plugin->save($submission, $data); 1498 1499 // Simulate a submission. 1500 $this->setUser($this->extrastudents[3]); 1501 $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true); 1502 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 1503 $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false); 1504 $data = new \stdClass(); 1505 $data->onlinetext_editor = array( 1506 'itemid' => file_get_unused_draft_itemid(), 1507 'text' => 'Submission text', 1508 'format' => FORMAT_MOODLE); 1509 $plugin = $assign->get_submission_plugin_by_type('onlinetext'); 1510 $plugin->save($submission, $data); 1511 1512 // Simulate adding a grade. 1513 $this->setUser($teacher); 1514 $data = new \stdClass(); 1515 $data->grade = '50.0'; 1516 $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0); 1517 $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0); 1518 1519 // Create a new submission with status NEW. 1520 $this->setUser($this->extrastudents[4]); 1521 $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true); 1522 1523 $this->assertEquals(2, $assign->count_grades()); 1524 $this->assertEquals(4, $assign->count_submissions()); 1525 $this->assertEquals(5, $assign->count_submissions(true)); 1526 $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED)); 1527 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT)); 1528 } 1529 1530 public function test_get_grading_userid_list_only_active() { 1531 $this->resetAfterTest(); 1532 1533 $course = $this->getDataGenerator()->create_course(); 1534 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1535 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1536 $suspendedstudent = $this->getDataGenerator()->create_and_enrol( 1537 $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 1538 1539 $this->setUser($teacher); 1540 1541 $assign = $this->create_instance($course); 1542 $this->assertCount(1, $assign->testable_get_grading_userid_list()); 1543 } 1544 1545 public function test_get_grading_userid_list_all() { 1546 $this->resetAfterTest(); 1547 1548 $course = $this->getDataGenerator()->create_course(); 1549 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1550 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1551 $suspendedstudent = $this->getDataGenerator()->create_and_enrol( 1552 $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 1553 1554 $this->setUser($teacher); 1555 set_user_preference('grade_report_showonlyactiveenrol', false); 1556 1557 $assign = $this->create_instance($course); 1558 $this->assertCount(2, $assign->testable_get_grading_userid_list()); 1559 } 1560 1561 public function test_cron() { 1562 global $PAGE; 1563 $this->resetAfterTest(); 1564 1565 // First run cron so there are no messages waiting to be sent (from other tests). 1566 cron_setup_user(); 1567 \assign::cron(); 1568 1569 $course = $this->getDataGenerator()->create_course(); 1570 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1571 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1572 1573 // Now create an assignment and add some feedback. 1574 $this->setUser($teacher); 1575 $assign = $this->create_instance($course, [ 1576 'sendstudentnotifications' => 1, 1577 ]); 1578 1579 $this->add_submission($student, $assign); 1580 $this->submit_for_grading($student, $assign); 1581 $this->mark_submission($teacher, $assign, $student, 50.0); 1582 1583 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1584 cron_setup_user(); 1585 $sink = $this->redirectMessages(); 1586 \assign::cron(); 1587 $messages = $sink->get_messages(); 1588 1589 $this->assertEquals(1, count($messages)); 1590 $this->assertEquals(1, $messages[0]->notification); 1591 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname); 1592 // Test customdata. 1593 $customdata = json_decode($messages[0]->customdata); 1594 $this->assertEquals($assign->get_course_module()->id, $customdata->cmid); 1595 $this->assertEquals($assign->get_instance()->id, $customdata->instance); 1596 $this->assertEquals('feedbackavailable', $customdata->messagetype); 1597 $userpicture = new \user_picture($teacher); 1598 $userpicture->size = 1; // Use f1 size. 1599 $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl); 1600 $this->assertEquals(0, $customdata->uniqueidforuser); // Not used in this case. 1601 $this->assertFalse($customdata->blindmarking); 1602 } 1603 1604 public function test_cron_without_notifications() { 1605 $this->resetAfterTest(); 1606 1607 // First run cron so there are no messages waiting to be sent (from other tests). 1608 cron_setup_user(); 1609 \assign::cron(); 1610 1611 $course = $this->getDataGenerator()->create_course(); 1612 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1613 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1614 1615 // Now create an assignment and add some feedback. 1616 $this->setUser($teacher); 1617 $assign = $this->create_instance($course, [ 1618 'sendstudentnotifications' => 1, 1619 ]); 1620 1621 $this->add_submission($student, $assign); 1622 $this->submit_for_grading($student, $assign); 1623 $this->mark_submission($teacher, $assign, $student, 50.0, [ 1624 'sendstudentnotifications' => 0, 1625 ]); 1626 1627 cron_setup_user(); 1628 $sink = $this->redirectMessages(); 1629 \assign::cron(); 1630 $messages = $sink->get_messages(); 1631 1632 $this->assertEquals(0, count($messages)); 1633 } 1634 1635 public function test_cron_regraded() { 1636 $this->resetAfterTest(); 1637 1638 // First run cron so there are no messages waiting to be sent (from other tests). 1639 cron_setup_user(); 1640 \assign::cron(); 1641 1642 $course = $this->getDataGenerator()->create_course(); 1643 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1644 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1645 1646 // Now create an assignment and add some feedback. 1647 $this->setUser($teacher); 1648 $assign = $this->create_instance($course, [ 1649 'sendstudentnotifications' => 1, 1650 ]); 1651 1652 $this->add_submission($student, $assign); 1653 $this->submit_for_grading($student, $assign); 1654 $this->mark_submission($teacher, $assign, $student, 50.0); 1655 1656 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1657 cron_setup_user(); 1658 \assign::cron(); 1659 1660 // Regrade. 1661 $this->mark_submission($teacher, $assign, $student, 50.0); 1662 1663 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1664 cron_setup_user(); 1665 $sink = $this->redirectMessages(); 1666 \assign::cron(); 1667 $messages = $sink->get_messages(); 1668 1669 $this->assertEquals(1, count($messages)); 1670 $this->assertEquals(1, $messages[0]->notification); 1671 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname); 1672 } 1673 1674 /** 1675 * Test delivery of grade notifications as controlled by marking workflow. 1676 */ 1677 public function test_markingworkflow_cron() { 1678 $this->resetAfterTest(); 1679 1680 // First run cron so there are no messages waiting to be sent (from other tests). 1681 cron_setup_user(); 1682 \assign::cron(); 1683 1684 $course = $this->getDataGenerator()->create_course(); 1685 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1686 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1687 1688 // Now create an assignment and add some feedback. 1689 $this->setUser($teacher); 1690 $assign = $this->create_instance($course, [ 1691 'sendstudentnotifications' => 1, 1692 'markingworkflow' => 1, 1693 ]); 1694 1695 // Mark a submission but set the workflowstate to an unreleased state. 1696 // This should not trigger a notification. 1697 $this->add_submission($student, $assign); 1698 $this->submit_for_grading($student, $assign); 1699 $this->mark_submission($teacher, $assign, $student, 50.0, [ 1700 'sendstudentnotifications' => 1, 1701 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE, 1702 ]); 1703 1704 cron_setup_user(); 1705 $sink = $this->redirectMessages(); 1706 \assign::cron(); 1707 $messages = $sink->get_messages(); 1708 1709 $this->assertEquals(0, count($messages)); 1710 1711 // Transition to the released state. 1712 $this->setUser($teacher); 1713 $submission = $assign->get_user_submission($student->id, true); 1714 $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED; 1715 $assign->testable_apply_grade_to_user($submission, $student->id, 0); 1716 1717 // Now run cron and see that one message was sent. 1718 cron_setup_user(); 1719 $sink = $this->redirectMessages(); 1720 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1721 \assign::cron(); 1722 $messages = $sink->get_messages(); 1723 1724 $this->assertEquals(1, count($messages)); 1725 $this->assertEquals(1, $messages[0]->notification); 1726 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname); 1727 } 1728 1729 public function test_cron_message_includes_courseid() { 1730 $this->resetAfterTest(); 1731 1732 // First run cron so there are no messages waiting to be sent (from other tests). 1733 cron_setup_user(); 1734 \assign::cron(); 1735 1736 $course = $this->getDataGenerator()->create_course(); 1737 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1738 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1739 1740 // Now create an assignment and add some feedback. 1741 $this->setUser($teacher); 1742 $assign = $this->create_instance($course, [ 1743 'sendstudentnotifications' => 1, 1744 ]); 1745 1746 // Mark a submission but set the workflowstate to an unreleased state. 1747 // This should not trigger a notification. 1748 $this->add_submission($student, $assign); 1749 $this->submit_for_grading($student, $assign); 1750 $this->mark_submission($teacher, $assign, $student); 1751 \phpunit_util::stop_message_redirection(); 1752 1753 // Now run cron and see that one message was sent. 1754 cron_setup_user(); 1755 $this->preventResetByRollback(); 1756 $sink = $this->redirectEvents(); 1757 $this->expectOutputRegex('/Done processing 1 assignment submissions/'); 1758 \assign::cron(); 1759 1760 $events = $sink->get_events(); 1761 $event = reset($events); 1762 $this->assertInstanceOf('\core\event\notification_sent', $event); 1763 $this->assertEquals($assign->get_course()->id, $event->other['courseid']); 1764 $sink->close(); 1765 } 1766 1767 public function test_is_graded() { 1768 $this->resetAfterTest(); 1769 1770 $course = $this->getDataGenerator()->create_course(); 1771 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1772 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1773 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1774 1775 $assign = $this->create_instance($course); 1776 1777 $this->add_submission($student, $assign); 1778 $this->submit_for_grading($student, $assign); 1779 $this->mark_submission($teacher, $assign, $student, 50.0); 1780 1781 $this->setUser($teacher); 1782 $this->assertEquals(true, $assign->testable_is_graded($student->id)); 1783 $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id)); 1784 } 1785 1786 public function test_can_grade() { 1787 global $DB; 1788 1789 $this->resetAfterTest(); 1790 1791 $course = $this->getDataGenerator()->create_course(); 1792 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1793 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1794 1795 $assign = $this->create_instance($course); 1796 1797 $this->setUser($student); 1798 $this->assertEquals(false, $assign->can_grade()); 1799 1800 $this->setUser($teacher); 1801 $this->assertEquals(true, $assign->can_grade()); 1802 1803 // Test the viewgrades capability for other users. 1804 $this->setUser(); 1805 $this->assertTrue($assign->can_grade($teacher->id)); 1806 $this->assertFalse($assign->can_grade($student->id)); 1807 1808 // Test the viewgrades capability - without mod/assign:grade. 1809 $this->setUser($student); 1810 1811 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 1812 assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id); 1813 $this->assertEquals(false, $assign->can_grade()); 1814 } 1815 1816 public function test_can_view_submission() { 1817 global $DB; 1818 1819 $this->resetAfterTest(); 1820 1821 $course = $this->getDataGenerator()->create_course(); 1822 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1823 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1824 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1825 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1826 $suspendedstudent = $this->getDataGenerator()->create_and_enrol( 1827 $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 1828 1829 $assign = $this->create_instance($course); 1830 1831 $this->setUser($student); 1832 $this->assertEquals(true, $assign->can_view_submission($student->id)); 1833 $this->assertEquals(false, $assign->can_view_submission($otherstudent->id)); 1834 $this->assertEquals(false, $assign->can_view_submission($teacher->id)); 1835 1836 $this->setUser($teacher); 1837 $this->assertEquals(true, $assign->can_view_submission($student->id)); 1838 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id)); 1839 $this->assertEquals(true, $assign->can_view_submission($teacher->id)); 1840 $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id)); 1841 1842 $this->setUser($editingteacher); 1843 $this->assertEquals(true, $assign->can_view_submission($student->id)); 1844 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id)); 1845 $this->assertEquals(true, $assign->can_view_submission($teacher->id)); 1846 $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id)); 1847 1848 // Test the viewgrades capability - without mod/assign:grade. 1849 $this->setUser($student); 1850 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 1851 assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id); 1852 $this->assertEquals(true, $assign->can_view_submission($student->id)); 1853 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id)); 1854 $this->assertEquals(true, $assign->can_view_submission($teacher->id)); 1855 $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id)); 1856 } 1857 1858 public function test_update_submission() { 1859 $this->resetAfterTest(); 1860 1861 $course = $this->getDataGenerator()->create_course(); 1862 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1863 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1864 1865 $assign = $this->create_instance($course); 1866 1867 $this->add_submission($student, $assign); 1868 $submission = $assign->get_user_submission($student->id, 0); 1869 $assign->testable_update_submission($submission, $student->id, true, true); 1870 1871 $this->setUser($teacher); 1872 1873 // Verify the gradebook update. 1874 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1875 1876 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1877 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified); 1878 } 1879 1880 public function test_update_submission_team() { 1881 $this->resetAfterTest(); 1882 1883 $course = $this->getDataGenerator()->create_course(); 1884 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1885 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1886 1887 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1888 groups_add_member($group, $student); 1889 1890 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1891 groups_add_member($group, $otherstudent); 1892 1893 $assign = $this->create_instance($course, [ 1894 'teamsubmission' => 1, 1895 ]); 1896 1897 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1898 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1899 $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified); 1900 1901 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id); 1902 $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id])); 1903 $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified); 1904 1905 $this->add_submission($student, $assign); 1906 $submission = $assign->get_group_submission($student->id, 0, true); 1907 $assign->testable_update_submission($submission, $student->id, true, true); 1908 1909 // Verify the gradebook update for the student. 1910 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1911 1912 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1913 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified); 1914 1915 // Verify the gradebook update for the other student. 1916 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id); 1917 1918 $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id])); 1919 $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified); 1920 } 1921 1922 public function test_update_submission_suspended() { 1923 $this->resetAfterTest(); 1924 1925 $course = $this->getDataGenerator()->create_course(); 1926 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1927 $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 1928 1929 $assign = $this->create_instance($course); 1930 1931 $this->add_submission($student, $assign); 1932 $submission = $assign->get_user_submission($student->id, 0); 1933 $assign->testable_update_submission($submission, $student->id, true, false); 1934 1935 $this->setUser($teacher); 1936 1937 // Verify the gradebook update. 1938 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1939 1940 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1941 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified); 1942 } 1943 1944 public function test_update_submission_blind() { 1945 $this->resetAfterTest(); 1946 1947 $course = $this->getDataGenerator()->create_course(); 1948 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1949 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1950 1951 $assign = $this->create_instance($course, [ 1952 'blindmarking' => 1, 1953 ]); 1954 1955 $this->add_submission($student, $assign); 1956 $submission = $assign->get_user_submission($student->id, 0); 1957 $assign->testable_update_submission($submission, $student->id, true, false); 1958 1959 // Verify the gradebook update. 1960 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id); 1961 1962 // The usermodified is not set because this is blind marked. 1963 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id])); 1964 $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified); 1965 } 1966 1967 public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() { 1968 global $PAGE; 1969 1970 $this->resetAfterTest(); 1971 1972 $course = $this->getDataGenerator()->create_course(); 1973 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 1974 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 1975 1976 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1977 groups_add_member($group, $student); 1978 1979 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1980 groups_add_member($group, $otherstudent); 1981 1982 $assign = $this->create_instance($course, [ 1983 'teamsubmission' => 1, 1984 'assignsubmission_onlinetext_enabled' => 1, 1985 'submissiondrafts' => 1, 1986 'requireallteammemberssubmit' => 1, 1987 ]); 1988 1989 // Now verify group assignments. 1990 $this->setUser($teacher); 1991 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 1992 1993 // Add a submission. 1994 $this->add_submission($student, $assign); 1995 1996 // Check we can see the submit button. 1997 $this->setUser($student); 1998 $output = $assign->view_student_summary($student, true); 1999 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output); 2000 2001 $submission = $assign->get_group_submission($student->id, 0, true); 2002 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 2003 $assign->testable_update_submission($submission, $student->id, true, true); 2004 2005 // Check that the student does not see "Submit" button. 2006 $output = $assign->view_student_summary($student, true); 2007 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output); 2008 2009 // Change to another user in the same group. 2010 $this->setUser($otherstudent); 2011 $output = $assign->view_student_summary($otherstudent, true); 2012 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output); 2013 2014 $submission = $assign->get_group_submission($otherstudent->id, 0, true); 2015 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 2016 $assign->testable_update_submission($submission, $otherstudent->id, true, true); 2017 $output = $assign->view_student_summary($otherstudent, true); 2018 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output); 2019 } 2020 2021 public function test_group_submissions_submit_for_marking() { 2022 global $PAGE; 2023 2024 $this->resetAfterTest(); 2025 2026 $course = $this->getDataGenerator()->create_course(); 2027 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2028 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2029 2030 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2031 groups_add_member($group, $student); 2032 2033 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2034 groups_add_member($group, $otherstudent); 2035 2036 // Now verify group assignments. 2037 $this->setUser($teacher); 2038 $time = time(); 2039 $assign = $this->create_instance($course, [ 2040 'teamsubmission' => 1, 2041 'assignsubmission_onlinetext_enabled' => 1, 2042 'submissiondrafts' => 1, 2043 'requireallteammemberssubmit' => 0, 2044 'duedate' => $time - (2 * DAYSECS), 2045 ]); 2046 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2047 2048 // Add a submission. 2049 $this->add_submission($student, $assign); 2050 2051 // Check we can see the submit button. 2052 $output = $assign->view_student_summary($student, true); 2053 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output); 2054 $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output); 2055 $difftime = time() - $time; 2056 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output); 2057 2058 $submission = $assign->get_group_submission($student->id, 0, true); 2059 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 2060 $assign->testable_update_submission($submission, $student->id, true, true); 2061 2062 // Check that the student does not see "Submit" button. 2063 $output = $assign->view_student_summary($student, true); 2064 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output); 2065 2066 // Change to another user in the same group. 2067 $this->setUser($otherstudent); 2068 $output = $assign->view_student_summary($otherstudent, true); 2069 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output); 2070 2071 // Check that time remaining is not overdue. 2072 $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output); 2073 $difftime = time() - $time; 2074 $this->assertStringContainsString(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output); 2075 2076 $submission = $assign->get_group_submission($otherstudent->id, 0, true); 2077 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 2078 $assign->testable_update_submission($submission, $otherstudent->id, true, true); 2079 $output = $assign->view_student_summary($otherstudent, true); 2080 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output); 2081 } 2082 2083 public function test_submissions_open() { 2084 global $DB; 2085 2086 $this->resetAfterTest(); 2087 2088 $course = $this->getDataGenerator()->create_course(); 2089 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2090 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2091 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2092 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2093 $suspendedstudent = $this->getDataGenerator()->create_and_enrol( 2094 $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 2095 2096 $this->setAdminUser(); 2097 2098 $now = time(); 2099 $tomorrow = $now + DAYSECS; 2100 $oneweek = $now + WEEKSECS; 2101 $yesterday = $now - DAYSECS; 2102 2103 $assign = $this->create_instance($course); 2104 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2105 2106 $assign = $this->create_instance($course, ['duedate' => $tomorrow]); 2107 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2108 2109 $assign = $this->create_instance($course, ['duedate' => $yesterday]); 2110 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2111 2112 $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]); 2113 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2114 2115 $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]); 2116 $this->assertEquals(false, $assign->testable_submissions_open($student->id)); 2117 2118 $assign->testable_save_user_extension($student->id, $tomorrow); 2119 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2120 2121 $assign = $this->create_instance($course, ['submissiondrafts' => 1]); 2122 $this->assertEquals(true, $assign->testable_submissions_open($student->id)); 2123 2124 $this->setUser($student); 2125 $submission = $assign->get_user_submission($student->id, true); 2126 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 2127 $assign->testable_update_submission($submission, $student->id, true, false); 2128 2129 $this->setUser($teacher); 2130 $this->assertEquals(false, $assign->testable_submissions_open($student->id)); 2131 } 2132 2133 public function test_get_graders() { 2134 global $DB; 2135 2136 $this->resetAfterTest(); 2137 2138 $course = $this->getDataGenerator()->create_course(); 2139 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2140 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2141 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2142 2143 $this->setAdminUser(); 2144 2145 // Create an assignment with no groups. 2146 $assign = $this->create_instance($course); 2147 $this->assertCount(2, $assign->testable_get_graders($student->id)); 2148 } 2149 2150 public function test_get_graders_separate_groups() { 2151 global $DB; 2152 2153 $this->resetAfterTest(); 2154 2155 $course = $this->getDataGenerator()->create_course(); 2156 $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2157 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2158 $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2159 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2160 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2161 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2162 2163 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2164 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2165 groups_add_member($group1, $student); 2166 2167 $this->setAdminUser(); 2168 2169 // Force create an assignment with SEPARATEGROUPS. 2170 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2171 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2172 2173 $assign = $this->create_instance($course, [ 2174 'groupingid' => $grouping->id, 2175 'groupmode' => SEPARATEGROUPS, 2176 ]); 2177 2178 $this->assertCount(4, $assign->testable_get_graders($student->id)); 2179 2180 // Note the second student is in a group that is not in the grouping. 2181 // This means that we get all graders that are not in a group in the grouping. 2182 $this->assertCount(4, $assign->testable_get_graders($otherstudent->id)); 2183 } 2184 2185 public function test_get_notified_users() { 2186 global $CFG, $DB; 2187 2188 $this->resetAfterTest(); 2189 2190 $course = $this->getDataGenerator()->create_course(); 2191 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2192 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2193 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 2194 2195 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2196 groups_add_member($group1, $teacher); 2197 2198 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2199 groups_add_member($group1, $editingteacher); 2200 2201 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2202 groups_add_member($group1, $student); 2203 2204 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2205 $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2206 2207 $capability = 'mod/assign:receivegradernotifications'; 2208 $coursecontext = \context_course::instance($course->id); 2209 $role = $DB->get_record('role', array('shortname' => 'teacher')); 2210 2211 $this->setUser($teacher); 2212 2213 // Create an assignment with no groups. 2214 $assign = $this->create_instance($course); 2215 2216 $this->assertCount(3, $assign->testable_get_notifiable_users($student->id)); 2217 2218 // Change nonediting teachers role to not receive grader notifications. 2219 assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext); 2220 2221 // Only the editing teachers will be returned. 2222 $this->assertCount(1, $assign->testable_get_notifiable_users($student->id)); 2223 2224 // Note the second student is in a group that is not in the grouping. 2225 // This means that we get all graders that are not in a group in the grouping. 2226 $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id)); 2227 } 2228 2229 public function test_get_notified_users_in_grouping() { 2230 global $CFG, $DB; 2231 2232 $this->resetAfterTest(); 2233 2234 $course = $this->getDataGenerator()->create_course(); 2235 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2236 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2237 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id)); 2238 2239 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2240 groups_add_member($group1, $teacher); 2241 2242 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 2243 groups_add_member($group1, $editingteacher); 2244 2245 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2246 groups_add_member($group1, $student); 2247 2248 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2249 $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2250 2251 // Force create an assignment with SEPARATEGROUPS. 2252 $assign = $this->create_instance($course, [ 2253 'groupingid' => $grouping->id, 2254 'groupmode' => SEPARATEGROUPS, 2255 ]); 2256 2257 // Student is in a group - only the tacher and editing teacher in the group shoudl be present. 2258 $this->setUser($student); 2259 $this->assertCount(2, $assign->testable_get_notifiable_users($student->id)); 2260 2261 // Note the second student is in a group that is not in the grouping. 2262 // This means that we get all graders that are not in a group in the grouping. 2263 $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id)); 2264 2265 // Change nonediting teachers role to not receive grader notifications. 2266 $capability = 'mod/assign:receivegradernotifications'; 2267 $coursecontext = \context_course::instance($course->id); 2268 $role = $DB->get_record('role', ['shortname' => 'teacher']); 2269 assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext); 2270 2271 // Only the editing teachers will be returned. 2272 $this->assertCount(1, $assign->testable_get_notifiable_users($student->id)); 2273 2274 // Note the second student is in a group that is not in the grouping. 2275 // This means that we get all graders that are not in a group in the grouping. 2276 // Unfortunately there are no editing teachers who are not in a group. 2277 $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id)); 2278 } 2279 2280 public function test_group_members_only() { 2281 global $CFG; 2282 2283 $this->resetAfterTest(); 2284 2285 $course = $this->getDataGenerator()->create_course(); 2286 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 2287 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2288 $this->getDataGenerator()->create_grouping_group([ 2289 'groupid' => $group1->id, 2290 'groupingid' => $grouping->id, 2291 ]); 2292 2293 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2294 $this->getDataGenerator()->create_grouping_group([ 2295 'groupid' => $group2->id, 2296 'groupingid' => $grouping->id, 2297 ]); 2298 2299 $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 2300 2301 // Add users in the following groups 2302 // - Teacher - Group 1. 2303 // - Student - Group 1. 2304 // - Student - Group 2. 2305 // - Student - Unrelated Group 2306 // - Student - No group. 2307 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2308 groups_add_member($group1, $teacher); 2309 2310 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2311 groups_add_member($group1, $student); 2312 2313 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2314 groups_add_member($group2, $otherstudent); 2315 2316 $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2317 groups_add_member($group2, $otherstudent); 2318 2319 $this->getDataGenerator()->create_and_enrol($course, 'student'); 2320 2321 $this->setAdminUser(); 2322 2323 $CFG->enableavailability = true; 2324 $assign = $this->create_instance($course, [], [ 2325 'availability' => json_encode( 2326 \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()]) 2327 ), 2328 'groupingid' => $grouping->id, 2329 ]); 2330 2331 // The two students in groups should be returned, but not the teacher in the group, or the student not in the 2332 // group, or the student in an unrelated group. 2333 $this->setUser($teacher); 2334 $participants = $assign->list_participants(0, true); 2335 $this->assertCount(2, $participants); 2336 $this->assertTrue(isset($participants[$student->id])); 2337 $this->assertTrue(isset($participants[$otherstudent->id])); 2338 } 2339 2340 public function test_get_uniqueid_for_user() { 2341 $this->resetAfterTest(); 2342 2343 $course = $this->getDataGenerator()->create_course(); 2344 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2345 $students = []; 2346 for ($i = 0; $i < 10; $i++) { 2347 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2348 $students[$student->id] = $student; 2349 } 2350 2351 $this->setUser($teacher); 2352 $assign = $this->create_instance($course); 2353 2354 foreach ($students as $student) { 2355 $uniqueid = $assign->get_uniqueid_for_user($student->id); 2356 $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid)); 2357 } 2358 } 2359 2360 public function test_show_student_summary() { 2361 global $CFG, $PAGE; 2362 2363 $this->resetAfterTest(); 2364 2365 $course = $this->getDataGenerator()->create_course(); 2366 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2367 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2368 $this->setUser($teacher); 2369 $assign = $this->create_instance($course); 2370 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2371 2372 // No feedback should be available because this student has not been graded. 2373 $this->setUser($student); 2374 $output = $assign->view_student_summary($student, true); 2375 $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if there is no grade'); 2376 2377 // Simulate adding a grade. 2378 $this->add_submission($student, $assign); 2379 $this->submit_for_grading($student, $assign); 2380 $this->mark_submission($teacher, $assign, $student); 2381 2382 // Now we should see the feedback. 2383 $this->setUser($student); 2384 $output = $assign->view_student_summary($student, true); 2385 $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade'); 2386 2387 // Now hide the grade in gradebook. 2388 $this->setUser($teacher); 2389 require_once($CFG->libdir.'/gradelib.php'); 2390 $gradeitem = new \grade_item(array( 2391 'itemtype' => 'mod', 2392 'itemmodule' => 'assign', 2393 'iteminstance' => $assign->get_instance()->id, 2394 'courseid' => $course->id)); 2395 2396 $gradeitem->set_hidden(1, false); 2397 2398 // No feedback should be available because the grade is hidden. 2399 $this->setUser($student); 2400 $output = $assign->view_student_summary($student, true); 2401 $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 2402 'Do not show feedback if the grade is hidden in the gradebook'); 2403 2404 // Freeze the context. 2405 $this->setAdminUser(); 2406 $context = $assign->get_context(); 2407 $CFG->contextlocking = true; 2408 $context->set_locked(true); 2409 2410 // No feedback should be available because the grade is hidden. 2411 $this->setUser($student); 2412 $output = $assign->view_student_summary($student, true); 2413 $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook'); 2414 2415 // Show the feedback again - it should still be visible even in a frozen context. 2416 $this->setUser($teacher); 2417 $gradeitem->set_hidden(0, false); 2418 2419 $this->setUser($student); 2420 $output = $assign->view_student_summary($student, true); 2421 $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade'); 2422 } 2423 2424 public function test_show_student_summary_with_feedback() { 2425 global $CFG, $PAGE; 2426 2427 $this->resetAfterTest(); 2428 2429 $course = $this->getDataGenerator()->create_course(); 2430 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2431 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2432 $this->setUser($teacher); 2433 $assign = $this->create_instance($course, [ 2434 'assignfeedback_comments_enabled' => 1 2435 ]); 2436 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2437 2438 // No feedback should be available because this student has not been graded. 2439 $this->setUser($student); 2440 $output = $assign->view_student_summary($student, true); 2441 $this->assertDoesNotMatchRegularExpression('/Feedback/', $output); 2442 2443 // Simulate adding a grade. 2444 $this->add_submission($student, $assign); 2445 $this->submit_for_grading($student, $assign); 2446 $this->mark_submission($teacher, $assign, $student, null, [ 2447 'assignfeedbackcomments_editor' => [ 2448 'text' => 'Tomato sauce', 2449 'format' => FORMAT_MOODLE, 2450 ], 2451 ]); 2452 2453 // Should have feedback but no grade. 2454 $this->setUser($student); 2455 $output = $assign->view_student_summary($student, true); 2456 $this->assertMatchesRegularExpression('/Feedback/', $output); 2457 $this->assertMatchesRegularExpression('/Tomato sauce/', $output); 2458 $this->assertDoesNotMatchRegularExpression('/Grade/', $output, 'Do not show grade when there is no grade.'); 2459 $this->assertDoesNotMatchRegularExpression('/Graded on/', $output, 'Do not show graded date when there is no grade.'); 2460 2461 // Add a grade now. 2462 $this->mark_submission($teacher, $assign, $student, 50.0, [ 2463 'assignfeedbackcomments_editor' => [ 2464 'text' => 'Bechamel sauce', 2465 'format' => FORMAT_MOODLE, 2466 ], 2467 ]); 2468 2469 // Should have feedback but no grade. 2470 $this->setUser($student); 2471 $output = $assign->view_student_summary($student, true); 2472 $this->assertDoesNotMatchRegularExpression('/Tomato sauce/', $output); 2473 $this->assertMatchesRegularExpression('/Bechamel sauce/', $output); 2474 $this->assertMatchesRegularExpression('/Grade/', $output); 2475 $this->assertMatchesRegularExpression('/Graded on/', $output); 2476 2477 // Now hide the grade in gradebook. 2478 $this->setUser($teacher); 2479 $gradeitem = new \grade_item(array( 2480 'itemtype' => 'mod', 2481 'itemmodule' => 'assign', 2482 'iteminstance' => $assign->get_instance()->id, 2483 'courseid' => $course->id)); 2484 2485 $gradeitem->set_hidden(1, false); 2486 2487 // No feedback should be available because the grade is hidden. 2488 $this->setUser($student); 2489 $output = $assign->view_student_summary($student, true); 2490 $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 2491 'Do not show feedback if the grade is hidden in the gradebook'); 2492 } 2493 2494 /** 2495 * Test reopen behavior when in "Manual" mode. 2496 */ 2497 public function test_attempt_reopen_method_manual() { 2498 global $PAGE; 2499 2500 $this->resetAfterTest(); 2501 $course = $this->getDataGenerator()->create_course(); 2502 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 2503 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 2504 2505 $assign = $this->create_instance($course, [ 2506 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL, 2507 'maxattempts' => 3, 2508 'submissiondrafts' => 1, 2509 'assignsubmission_onlinetext_enabled' => 1, 2510 ]); 2511 $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id])); 2512 2513 // Student should be able to see an add submission button. 2514 $this->setUser($student); 2515 $output = $assign->view_student_summary($student, true); 2516 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign'))); 2517 2518 // Add a submission. 2519 $this->add_submission($student, $assign); 2520 $this->submit_for_grading($student, $assign); 2521 2522 // Verify the student cannot make changes to the submission. 2523 $output = $assign->view_student_summary($student, true); 2524 $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign'))); 2525 2526 // Mark the submission. 2527 $this->mark_submission($teacher, $assign, $student); 2528 2529 // Check the student can see the grade. 2530 $this->setUser($student); 2531 $output = $assign->view_student_summary($student, true); 2532 $this->assertNotEquals(false, strpos($output, '50.0')); 2533 2534 // Allow the student another attempt. 2535 $teacher->ignoresesskey = true; 2536 $this->setUser($teacher); 2537 $result = $assign->testable_process_add_attempt($student->id); 2538 $this->assertEquals(true, $result); 2539 2540 // Check that the previous attempt is now in the submission history table. 2541 $this->setUser($student); 2542 $output = $assign->view_student_summary($student, true); 2543 // Need a better check. 2544 $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text'); 2545 2546 // Check that the student now has a button for Add a new attempt". 2547 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign'))); 2548 // Check that the student now does not have a button for Submit. 2549 $this->assertEquals(false, strpos($output