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