2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Unit tests for (some of) mod/assign/locallib.php.
22 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') || die();
30 require_once($CFG->dirroot . '/mod/assign/locallib.php');
31 require_once($CFG->dirroot . '/mod/assign/upgradelib.php');
32 require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
35 * Unit tests for (some of) mod/assign/locallib.php.
37 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 class mod_assign_locallib_testcase extends advanced_testcase {
42 // Use the generator helper.
43 use mod_assign_test_generator;
45 public function test_return_links() {
48 $this->resetAfterTest();
49 $course = $this->getDataGenerator()->create_course();
51 $assign = $this->create_instance($course);
52 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
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());
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');
64 $this->setUser($teacher);
65 $assign = $this->create_instance($course);
66 $installedplugins = array_keys(core_component::get_plugin_list('assignfeedback'));
68 foreach ($assign->get_feedback_plugins() as $plugin) {
69 $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins');
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');
78 $this->setUser($teacher);
79 $assign = $this->create_instance($course);
80 $installedplugins = array_keys(core_component::get_plugin_list('assignsubmission'));
82 foreach ($assign->get_submission_plugins() as $plugin) {
83 $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins');
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');
93 $this->setUser($teacher);
94 $assign = $this->create_instance($course, ['blindmarking' => 1]);
95 $this->assertEquals(true, $assign->is_blind_marking());
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')));
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;
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;
118 // Test sesskey is required.
119 $this->setUser($teacher);
120 $this->expectException('moodle_exception');
121 $assign->reveal_identities();
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;
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')));
135 // Set this back to default.
136 $teacher->ignoresesskey = false;
140 * Data provider for test_get_assign_perpage
142 * @return array Provider data
144 public function get_assign_perpage_provider() {
148 'userprefs' => array(
157 'userprefs' => array(
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
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');
180 $this->setUser($teacher);
181 $assign = $this->create_instance($course);
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());
193 * Test filter by requires grading.
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.
198 public function test_gradingtable_filter_by_requiresgrading_no_grade() {
201 $this->resetAfterTest();
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
212 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
213 'id' => $assign->get_course_module()->id,
214 'action' => 'grading',
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);
221 // Test that the filter function does not throw errors for assignments with no grade.
222 $this->assertStringContainsString(get_string('nothingtodisplay'), $output);
227 * Test submissions with extension date.
229 public function test_gradingtable_extension_due_date() {
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');
237 // Setup the assignment.
238 $this->setUser($teacher);
240 $assign = $this->create_instance($course, [
241 'assignsubmission_onlinetext_enabled' => 1,
242 'duedate' => time() - (4 * DAYSECS),
244 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
245 'id' => $assign->get_course_module()->id,
246 'action' => 'grading',
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);
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);
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,
274 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
275 $plugin->save($submission, $data);
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);
286 * Test that late submissions with extension date calculate correctly.
288 public function test_gradingtable_extension_date_calculation_for_lateness() {
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');
296 // Setup the assignment.
297 $this->setUser($teacher);
299 $assign = $this->create_instance($course, [
300 'assignsubmission_onlinetext_enabled' => 1,
301 'duedate' => time() - (4 * DAYSECS),
303 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
304 'id' => $assign->get_course_module()->id,
305 'action' => 'grading',
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);
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);
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,
335 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
336 $plugin->save($submission, $data);
337 $submittedtime = time();
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);
346 $difftime = $submittedtime - $time;
347 $this->assertStringContainsString(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
350 public function test_gradingtable_status_rendering() {
353 $this->resetAfterTest();
354 $course = $this->getDataGenerator()->create_course();
355 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
356 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
358 // Setup the assignment.
359 $this->setUser($teacher);
361 $assign = $this->create_instance($course, [
362 'assignsubmission_onlinetext_enabled' => 1,
363 'duedate' => $time - (4 * DAYSECS),
365 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
366 'id' => $assign->get_course_module()->id,
367 'action' => 'grading',
370 // Check that the assignment is late.
371 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
372 $output = $assign->get_renderer()->render($gradingtable);
373 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
374 $difftime = time() - $time;
375 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
377 // Simulate a student viewing the assignment without submitting.
378 $this->setUser($student);
379 $submission = $assign->get_user_submission($student->id, true);
380 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
381 $assign->testable_update_submission($submission, $student->id, true, false);
382 $submittedtime = time();
385 $this->setUser($teacher);
386 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
387 $output = $assign->get_renderer()->render($gradingtable);
388 $difftime = $submittedtime - $time;
389 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
391 $document = new DOMDocument();
392 @$document->loadHTML($output);
393 $xpath = new DOMXPath($document);
394 $this->assertEquals('', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])'));
398 * Check that group submission information is rendered correctly in the
401 public function test_gradingtable_group_submissions_rendering() {
404 $this->resetAfterTest();
405 $course = $this->getDataGenerator()->create_course();
406 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
408 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
409 groups_add_member($group, $teacher);
413 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
414 $students[] = $student;
415 groups_add_member($group, $student);
417 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
418 $students[] = $student;
419 groups_add_member($group, $student);
421 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
422 $students[] = $student;
423 groups_add_member($group, $student);
425 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
426 $students[] = $student;
427 groups_add_member($group, $student);
429 // Verify group assignments.
430 $this->setUser($teacher);
431 $assign = $this->create_instance($course, [
432 'teamsubmission' => 1,
433 'assignsubmission_onlinetext_enabled' => 1,
434 'submissiondrafts' => 1,
435 'requireallteammemberssubmit' => 0,
437 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
438 'id' => $assign->get_course_module()->id,
439 'action' => 'grading',
443 $this->setUser($student);
444 $data = new stdClass();
445 $data->onlinetext_editor = [
446 'itemid' => file_get_unused_draft_itemid(),
447 'text' => 'Submission text',
448 'format' => FORMAT_MOODLE,
451 $assign->save_submission($data, $notices);
453 $submission = $assign->get_group_submission($student->id, 0, true);
454 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
455 $assign->testable_update_submission($submission, $student->id, true, true);
458 $this->setUser($teacher);
459 $gradingtable = new assign_grading_table($assign, 4, '', 0, true);
460 $output = $assign->get_renderer()->render($gradingtable);
461 $document = new DOMDocument();
462 @$document->loadHTML($output);
463 $xpath = new DOMXPath($document);
466 $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c4"]/div[@class="submissionstatussubmitted"])'));
467 $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c4"]/div[@class="submissionstatussubmitted"])'));
469 // Check submission last modified date
470 $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])')));
471 $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c8"])')));
474 $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c5"])'));
475 $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c5"])'));
477 // Check submission text.
478 $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c9"]/div/div)'));
479 $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c9"]/div/div)'));
481 // Check comments can be made.
482 $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r0_c10"]//textarea)'));
483 $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r3_c10"]//textarea)'));
486 public function test_show_intro() {
487 $this->resetAfterTest();
488 $course = $this->getDataGenerator()->create_course();
489 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
491 // Test whether we are showing the intro at the correct times.
492 $this->setUser($teacher);
493 $assign = $this->create_instance($course, ['alwaysshowdescription' => 1]);
495 $this->assertEquals(true, $assign->testable_show_intro());
497 $tomorrow = time() + DAYSECS;
499 $assign = $this->create_instance($course, [
500 'alwaysshowdescription' => 0,
501 'allowsubmissionsfromdate' => $tomorrow,
503 $this->assertEquals(false, $assign->testable_show_intro());
504 $yesterday = time() - DAYSECS;
505 $assign = $this->create_instance($course, [
506 'alwaysshowdescription' => 0,
507 'allowsubmissionsfromdate' => $yesterday,
509 $this->assertEquals(true, $assign->testable_show_intro());
512 public function test_has_submissions_or_grades() {
513 $this->resetAfterTest();
514 $course = $this->getDataGenerator()->create_course();
515 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
516 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
518 $this->setUser($teacher);
519 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
520 $instance = $assign->get_instance();
522 // Should start empty.
523 $this->assertEquals(false, $assign->has_submissions_or_grades());
525 // Simulate a submission.
526 $this->setUser($student);
527 $submission = $assign->get_user_submission($student->id, true);
529 // The submission is still new.
530 $this->assertEquals(false, $assign->has_submissions_or_grades());
532 // Submit the submission.
533 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
534 $assign->testable_update_submission($submission, $student->id, true, false);
535 $data = new stdClass();
536 $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
537 'text'=>'Submission text',
538 'format'=>FORMAT_MOODLE);
539 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
540 $plugin->save($submission, $data);
543 $this->assertEquals(true, $assign->has_submissions_or_grades());
546 public function test_delete_grades() {
547 $this->resetAfterTest();
548 $course = $this->getDataGenerator()->create_course();
549 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
550 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
552 $this->setUser($teacher);
553 $assign = $this->create_instance($course);
555 // Simulate adding a grade.
556 $this->setUser($teacher);
557 $data = new stdClass();
558 $data->grade = '50.0';
559 $assign->testable_apply_grade_to_user($data, $student->id, 0);
561 // Now see if the data is in the gradebook.
562 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
564 $this->assertNotEquals(0, count($gradinginfo->items));
566 $assign->testable_delete_grades();
567 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
569 $this->assertEquals(0, count($gradinginfo->items));
572 public function test_delete_instance() {
573 $this->resetAfterTest();
574 $course = $this->getDataGenerator()->create_course();
575 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
576 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
578 $this->setUser($teacher);
579 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
581 // Simulate adding a grade.
582 $this->setUser($teacher);
583 $data = new stdClass();
584 $data->grade = '50.0';
585 $assign->testable_apply_grade_to_user($data, $student->id, 0);
587 // Simulate a submission.
588 $this->add_submission($student, $assign);
590 // Now try and delete.
591 $this->setUser($teacher);
592 $this->assertEquals(true, $assign->delete_instance());
595 public function test_reset_userdata() {
598 $this->resetAfterTest();
599 $course = $this->getDataGenerator()->create_course();
600 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
601 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
604 $this->setUser($teacher);
605 $assign = $this->create_instance($course, [
606 'assignsubmission_onlinetext_enabled' => 1,
610 // Simulate adding a grade.
611 $this->add_submission($student, $assign);
612 $this->submit_for_grading($student, $assign);
613 $this->mark_submission($teacher, $assign, $student, 50.0);
615 // Simulate a submission.
616 $this->setUser($student);
617 $submission = $assign->get_user_submission($student->id, true);
618 $data = new stdClass();
619 $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
620 'text'=>'Submission text',
621 'format'=>FORMAT_MOODLE);
622 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
623 $plugin->save($submission, $data);
625 $this->assertEquals(true, $assign->has_submissions_or_grades());
626 // Now try and reset.
627 $data = new stdClass();
628 $data->reset_assign_submissions = 1;
629 $data->reset_gradebook_grades = 1;
630 $data->reset_assign_user_overrides = 1;
631 $data->reset_assign_group_overrides = 1;
632 $data->courseid = $course->id;
633 $data->timeshift = DAYSECS;
634 $this->setUser($teacher);
635 $assign->reset_userdata($data);
636 $this->assertEquals(false, $assign->has_submissions_or_grades());
638 // Reload the instance data.
639 $instance = $DB->get_record('assign', array('id'=>$assign->get_instance()->id));
640 $this->assertEquals($now + DAYSECS, $instance->duedate);
642 // Test reset using assign_reset_userdata().
643 $assignduedate = $instance->duedate; // Keep old updated value for comparison.
644 $data->timeshift = (2 * DAYSECS);
645 assign_reset_userdata($data);
646 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
647 $this->assertEquals($assignduedate + (2 * DAYSECS), $instance->duedate);
649 // Create one more assignment and reset, make sure time shifted for previous assignment is not changed.
650 $assign2 = $this->create_instance($course, [
651 'assignsubmission_onlinetext_enabled' => 1,
654 $assignduedate = $instance->duedate;
655 $data->timeshift = 3*DAYSECS;
656 $assign2->reset_userdata($data);
657 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
658 $this->assertEquals($assignduedate, $instance->duedate);
659 $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
660 $this->assertEquals($now + 3*DAYSECS, $instance2->duedate);
662 // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date.
663 $assignduedate = $instance->duedate;
664 $assign2duedate = $instance2->duedate;
665 $data->timeshift = (4 * DAYSECS);
666 assign_reset_userdata($data);
667 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
668 $this->assertEquals($assignduedate + (4 * DAYSECS), $instance->duedate);
669 $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
670 $this->assertEquals($assign2duedate + (4 * DAYSECS), $instance2->duedate);
673 public function test_plugin_settings() {
676 $this->resetAfterTest();
678 $course = $this->getDataGenerator()->create_course();
679 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
682 $this->setUser($teacher);
683 $assign = $this->create_instance($course, [
684 'assignsubmission_file_enabled' => 1,
685 'assignsubmission_file_maxfiles' => 12,
686 'assignsubmission_file_maxsizebytes' => 10,
689 $plugin = $assign->get_submission_plugin_by_type('file');
690 $this->assertEquals('12', $plugin->get_config('maxfilesubmissions'));
693 public function test_update_calendar() {
696 $this->resetAfterTest();
698 $course = $this->getDataGenerator()->create_course();
699 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
701 $this->setUser($teacher);
702 $userctx = context_user::instance($teacher->id)->id;
704 // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey.
705 $draftid = file_get_unused_draft_itemid();
706 $_REQUEST['introeditor'] = $draftid;
707 $_POST['introeditor'] = $draftid;
708 $_POST['sesskey'] = sesskey();
710 // Write links to a draft area.
711 $fakearealink1 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">link</a>', 'draftfile.php', $userctx,
712 'user', 'draft', $draftid);
713 $fakearealink2 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">new</a>', 'draftfile.php', $userctx,
714 'user', 'draft', $draftid);
716 // Create a new assignment with links to a draft area.
718 $assign = $this->create_instance($course, [
720 'intro' => $fakearealink1,
721 'introformat' => FORMAT_HTML
724 // See if there is an event in the calendar.
725 $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id);
726 $event = $DB->get_record('event', $params);
727 $this->assertNotEmpty($event);
728 $this->assertSame('link', $event->description); // The pluginfile links are removed.
730 // Make sure the same works when updating the assignment.
731 $instance = $assign->get_instance();
732 $instance->instance = $instance->id;
733 $instance->intro = $fakearealink2;
734 $instance->introformat = FORMAT_HTML;
735 $assign->update_instance($instance);
736 $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
737 $event = $DB->get_record('event', $params);
738 $this->assertNotEmpty($event);
739 $this->assertSame('new', $event->description); // The pluginfile links are removed.
741 // Create an assignment with a description that should be hidden.
742 $assign = $this->create_instance($course, [
743 'duedate' => $now + 160,
744 'alwaysshowdescription' => false,
745 'allowsubmissionsfromdate' => $now + 60,
746 'intro' => 'Some text',
749 // Get the event from the calendar.
750 $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id);
751 $event = $DB->get_record('event', [
752 'modulename' => 'assign',
753 'instance' => $assign->get_instance()->id,
756 $this->assertEmpty($event->description);
758 // Change the allowsubmissionfromdate to the past - do this directly in the DB
759 // because if we call the assignment update method - it will update the calendar
760 // and we want to test that this works from cron.
761 $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id'=>$assign->get_instance()->id));
762 // Run cron to update the event in the calendar.
764 $event = $DB->get_record('event', $params);
766 $this->assertStringContainsString('Some text', $event->description);
770 public function test_update_instance() {
773 $this->resetAfterTest();
775 $course = $this->getDataGenerator()->create_course();
776 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
778 $this->setUser($teacher);
779 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
782 $instance = $assign->get_instance();
783 $instance->duedate = $now;
784 $instance->instance = $instance->id;
785 $instance->assignsubmission_onlinetext_enabled = 1;
787 $assign->update_instance($instance);
789 $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
790 $this->assertEquals($now, $instance->duedate);
793 public function test_cannot_submit_empty() {
796 $this->resetAfterTest();
798 $course = $this->getDataGenerator()->create_course();
799 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
801 $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
803 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
805 // Test you cannot see the submit button for an offline assignment regardless.
806 $this->setUser($student);
807 $output = $assign->view_student_summary($student, true);
808 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output, 'Can submit empty offline assignment');
811 public function test_cannot_submit_empty_no_submission() {
814 $this->resetAfterTest();
816 $course = $this->getDataGenerator()->create_course();
817 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
819 $assign = $this->create_instance($course, [
820 'submissiondrafts' => 1,
821 'assignsubmission_onlinetext_enabled' => 1,
824 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
826 // Test you cannot see the submit button for an online text assignment with no submission.
827 $this->setUser($student);
828 $output = $assign->view_student_summary($student, true);
829 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output, 'Cannot submit empty onlinetext assignment');
832 public function test_can_submit_with_submission() {
835 $this->resetAfterTest();
837 $course = $this->getDataGenerator()->create_course();
838 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
840 $assign = $this->create_instance($course, [
841 'submissiondrafts' => 1,
842 'assignsubmission_onlinetext_enabled' => 1,
845 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
848 $this->add_submission($student, $assign);
850 // Test you can see the submit button for an online text assignment with a submission.
851 $this->setUser($student);
852 $output = $assign->view_student_summary($student, true);
853 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output, 'Can submit non empty onlinetext assignment');
857 * Test new_submission_empty
859 * We only test combinations of plugins here. Individual plugins are tested
860 * in their respective test files.
862 * @dataProvider test_new_submission_empty_testcases
863 * @param string $data The file submission data
864 * @param bool $expected The expected return value
866 public function test_new_submission_empty($data, $expected) {
867 $this->resetAfterTest();
869 $course = $this->getDataGenerator()->create_course();
870 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
872 $assign = $this->create_instance($course, [
873 'assignsubmission_file_enabled' => 1,
874 'assignsubmission_file_maxfiles' => 12,
875 'assignsubmission_file_maxsizebytes' => 10,
876 'assignsubmission_onlinetext_enabled' => 1,
878 $this->setUser($student);
879 $submission = new stdClass();
881 if ($data['file'] && isset($data['file']['filename'])) {
882 $itemid = file_get_unused_draft_itemid();
883 $submission->files_filemanager = $itemid;
884 $data['file'] += ['contextid' => context_user::instance($student->id)->id, 'itemid' => $itemid];
885 $fs = get_file_storage();
886 $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']);
889 if ($data['onlinetext']) {
890 $submission->onlinetext_editor = ['text' => $data['onlinetext']];
893 $result = $assign->new_submission_empty($submission);
894 $this->assertTrue($result === $expected);
898 * Dataprovider for the test_new_submission_empty testcase
900 * @return array of testcases
902 public function test_new_submission_empty_testcases() {
904 'With file and onlinetext' => [
907 'component' => 'user',
908 'filearea' => 'draft',
910 'filename' => 'not_a_virus.exe'
912 'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu'
919 public function test_list_participants() {
922 $this->resetAfterTest();
924 $course = $this->getDataGenerator()->create_course();
925 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
927 // Create 10 students.
928 for ($i = 0; $i < 10; $i++) {
929 $this->getDataGenerator()->create_and_enrol($course, 'student');
932 $this->setUser($teacher);
933 $assign = $this->create_instance($course, ['grade' => 100]);
935 $this->assertCount(10, $assign->list_participants(null, true));
938 public function test_list_participants_activeenrol() {
941 $this->resetAfterTest();
943 $course = $this->getDataGenerator()->create_course();
944 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
946 // Create 10 students.
947 for ($i = 0; $i < 10; $i++) {
948 $this->getDataGenerator()->create_and_enrol($course, 'student');
951 // Create 10 suspended students.
952 for ($i = 0; $i < 10; $i++) {
953 $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
956 $this->setUser($teacher);
957 set_user_preference('grade_report_showonlyactiveenrol', false);
958 $assign = $this->create_instance($course, ['grade' => 100]);
960 $this->assertCount(10, $assign->list_participants(null, true));
963 public function test_list_participants_with_group_restriction() {
966 $this->resetAfterTest();
968 $course = $this->getDataGenerator()->create_course();
969 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
970 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
971 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
972 $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
974 // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group.
975 $CFG->enableavailability = true;
977 $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
978 $assign = $this->create_instance($course, [
980 'availability' => json_encode(
981 \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)])
985 groups_add_member($specialgroup, $student);
986 groups_add_member($specialgroup, $otherstudent);
987 $this->assertEquals(2, count($assign->list_participants(null, true)));
990 public function test_get_participant_user_not_exist() {
991 $this->resetAfterTest();
992 $course = $this->getDataGenerator()->create_course();
994 $assign = $this->create_instance($course);
995 $this->assertNull($assign->get_participant('-1'));
998 public function test_get_participant_not_enrolled() {
999 $this->resetAfterTest();
1000 $course = $this->getDataGenerator()->create_course();
1001 $assign = $this->create_instance($course);
1003 $user = $this->getDataGenerator()->create_user();
1004 $this->assertNull($assign->get_participant($user->id));
1007 public function test_get_participant_no_submission() {
1008 $this->resetAfterTest();
1009 $course = $this->getDataGenerator()->create_course();
1010 $assign = $this->create_instance($course);
1011 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1013 $participant = $assign->get_participant($student->id);
1015 $this->assertEquals($student->id, $participant->id);
1016 $this->assertFalse($participant->submitted);
1017 $this->assertFalse($participant->requiregrading);
1018 $this->assertFalse($participant->grantedextension);
1021 public function test_get_participant_granted_extension() {
1022 $this->resetAfterTest();
1023 $course = $this->getDataGenerator()->create_course();
1024 $assign = $this->create_instance($course);
1025 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1026 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1028 $this->setUser($teacher);
1029 $assign->save_user_extension($student->id, time());
1030 $participant = $assign->get_participant($student->id);
1032 $this->assertEquals($student->id, $participant->id);
1033 $this->assertFalse($participant->submitted);
1034 $this->assertFalse($participant->requiregrading);
1035 $this->assertTrue($participant->grantedextension);
1038 public function test_get_participant_with_ungraded_submission() {
1039 $this->resetAfterTest();
1040 $course = $this->getDataGenerator()->create_course();
1041 $assign = $this->create_instance($course);
1042 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1043 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1045 // Simulate a submission.
1046 $this->add_submission($student, $assign);
1047 $this->submit_for_grading($student, $assign);
1049 $participant = $assign->get_participant($student->id);
1051 $this->assertEquals($student->id, $participant->id);
1052 $this->assertTrue($participant->submitted);
1053 $this->assertTrue($participant->requiregrading);
1054 $this->assertFalse($participant->grantedextension);
1057 public function test_get_participant_with_graded_submission() {
1058 $this->resetAfterTest();
1059 $course = $this->getDataGenerator()->create_course();
1060 $assign = $this->create_instance($course);
1061 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1062 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1064 // Simulate a submission.
1065 $this->add_submission($student, $assign);
1066 $this->submit_for_grading($student, $assign);
1068 $this->mark_submission($teacher, $assign, $student, 50.0);
1070 $data = new stdClass();
1071 $data->grade = '50.0';
1072 $assign->testable_apply_grade_to_user($data, $student->id, 0);
1074 $participant = $assign->get_participant($student->id);
1076 $this->assertEquals($student->id, $participant->id);
1077 $this->assertTrue($participant->submitted);
1078 $this->assertFalse($participant->requiregrading);
1079 $this->assertFalse($participant->grantedextension);
1083 * No active group and non-group submissions disallowed => 2 groups.
1085 public function test_count_teams_no_active_non_group_allowed() {
1086 $this->resetAfterTest();
1088 $course = $this->getDataGenerator()->create_course();
1089 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1090 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1091 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1093 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1094 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1095 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1096 groups_add_member($group1, $student1);
1097 groups_add_member($group2, $student2);
1099 $this->setUser($teacher);
1100 $assign = $this->create_instance($course, ['teamsubmission' => 1]);
1102 $this->assertEquals(2, $assign->count_teams());
1106 * No active group and non group submissions allowed => 2 groups + the default one.
1108 public function test_count_teams_non_group_allowed() {
1109 $this->resetAfterTest();
1111 $course = $this->getDataGenerator()->create_course();
1112 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1113 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1114 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1115 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1117 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1118 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1120 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1121 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1122 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1124 groups_add_member($group1, $student1);
1125 groups_add_member($group2, $student2);
1127 $assign = $this->create_instance($course, [
1128 'teamsubmission' => 1,
1129 'teamsubmissiongroupingid' => $grouping->id,
1130 'preventsubmissionnotingroup' => false,
1133 $this->setUser($teacher);
1134 $this->assertEquals(3, $assign->count_teams());
1136 // Active group only.
1137 $this->assertEquals(1, $assign->count_teams($group1->id));
1138 $this->assertEquals(1, $assign->count_teams($group2->id));
1142 * Active group => just selected one.
1144 public function test_count_teams_no_active_group() {
1145 $this->resetAfterTest();
1147 $course = $this->getDataGenerator()->create_course();
1148 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1149 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1150 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1151 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1153 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1154 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1156 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1157 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1158 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1160 groups_add_member($group1, $student1);
1161 groups_add_member($group2, $student2);
1163 $assign = $this->create_instance($course, [
1164 'teamsubmission' => 1,
1165 'preventsubmissionnotingroup' => true,
1168 $this->setUser($teacher);
1169 $this->assertEquals(2, $assign->count_teams());
1171 // Active group only.
1172 $this->assertEquals(1, $assign->count_teams($group1->id));
1173 $this->assertEquals(1, $assign->count_teams($group2->id));
1177 * Active group => just selected one.
1179 public function test_count_teams_groups_only() {
1180 $this->resetAfterTest();
1182 $course = $this->getDataGenerator()->create_course();
1183 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1185 $assign = $this->create_instance($course, [
1186 'teamsubmission' => 1,
1187 'teamsubmissiongroupingid' => $grouping->id,
1188 'preventsubmissionnotingroup' => false,
1190 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1192 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1193 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1194 groups_add_member($group1, $student1);
1196 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1197 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1198 groups_add_member($group2, $student2);
1200 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1201 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1203 $this->setUser($teacher);
1205 $assign = $this->create_instance($course, [
1206 'teamsubmission' => 1,
1207 'preventsubmissionnotingroup' => true,
1209 $this->assertEquals(2, $assign->count_teams());
1212 public function test_submit_to_default_group() {
1213 global $DB, $SESSION;
1215 $this->resetAfterTest();
1217 $course = $this->getDataGenerator()->create_course();
1218 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1219 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1221 $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1222 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1224 $assign = $this->create_instance($course, [
1225 'teamsubmission' => 1,
1226 'assignsubmission_onlinetext_enabled' => 1,
1227 'submissiondrafts' => 0,
1228 'groupmode' => VISIBLEGROUPS,
1231 $usergroup = $assign->get_submission_group($student->id);
1232 $this->assertFalse($usergroup, 'New student is in default group');
1234 // Add a submission.
1235 $this->add_submission($student, $assign);
1236 $this->submit_for_grading($student, $assign);
1238 // Set active groups to all groups.
1239 $this->setUser($teacher);
1240 $SESSION->activegroup[$course->id]['aag'][0] = 0;
1241 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1243 // Set an active group.
1244 $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id;
1245 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1248 public function test_count_submissions_no_draft() {
1249 $this->resetAfterTest();
1251 $course = $this->getDataGenerator()->create_course();
1252 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1253 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1255 $assign = $this->create_instance($course, [
1256 'assignsubmission_onlinetext_enabled' => 1,
1259 $assign->get_user_submission($student->id, true);
1261 // Note: Drafts count as a submission.
1262 $this->assertEquals(0, $assign->count_grades());
1263 $this->assertEquals(0, $assign->count_submissions());
1264 $this->assertEquals(1, $assign->count_submissions(true));
1265 $this->assertEquals(0, $assign->count_submissions_need_grading());
1266 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1267 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1268 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1269 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1272 public function test_count_submissions_draft() {
1273 $this->resetAfterTest();
1275 $course = $this->getDataGenerator()->create_course();
1276 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1277 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1279 $assign = $this->create_instance($course, [
1280 'assignsubmission_onlinetext_enabled' => 1,
1283 $this->add_submission($student, $assign);
1285 // Note: Drafts count as a submission.
1286 $this->assertEquals(0, $assign->count_grades());
1287 $this->assertEquals(1, $assign->count_submissions());
1288 $this->assertEquals(1, $assign->count_submissions(true));
1289 $this->assertEquals(0, $assign->count_submissions_need_grading());
1290 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1291 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1292 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1293 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1296 public function test_count_submissions_submitted() {
1299 $this->resetAfterTest();
1301 $course = $this->getDataGenerator()->create_course();
1302 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1303 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1305 $assign = $this->create_instance($course, [
1306 'assignsubmission_onlinetext_enabled' => 1,
1309 $this->add_submission($student, $assign);
1310 $this->submit_for_grading($student, $assign);
1312 $this->assertEquals(0, $assign->count_grades());
1313 $this->assertEquals(1, $assign->count_submissions());
1314 $this->assertEquals(1, $assign->count_submissions(true));
1315 $this->assertEquals(1, $assign->count_submissions_need_grading());
1316 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1317 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1318 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1319 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1322 public function test_count_submissions_graded() {
1323 $this->resetAfterTest();
1325 $course = $this->getDataGenerator()->create_course();
1326 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1327 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1329 $assign = $this->create_instance($course, [
1330 'assignsubmission_onlinetext_enabled' => 1,
1333 $this->add_submission($student, $assign);
1334 $this->submit_for_grading($student, $assign);
1335 $this->mark_submission($teacher, $assign, $student, 50.0);
1337 // Although it has been graded, it is still marked as submitted.
1338 $this->assertEquals(1, $assign->count_grades());
1339 $this->assertEquals(1, $assign->count_submissions());
1340 $this->assertEquals(1, $assign->count_submissions(true));
1341 $this->assertEquals(0, $assign->count_submissions_need_grading());
1342 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1343 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1344 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1345 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1348 public function test_count_submissions_graded_group() {
1351 $this->resetAfterTest();
1353 $course = $this->getDataGenerator()->create_course();
1354 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1355 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1356 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1357 $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1358 groups_add_member($group, $student);
1360 $assign = $this->create_instance($course, [
1361 'assignsubmission_onlinetext_enabled' => 1,
1362 'groupmode' => VISIBLEGROUPS,
1365 $this->add_submission($student, $assign);
1366 $this->submit_for_grading($student, $assign);
1368 // The user should still be listed when fetching all groups.
1369 $this->setUser($teacher);
1370 $SESSION->activegroup[$course->id]['aag'][0] = 0;
1371 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1373 // The user should still be listed when fetching just their group.
1374 $SESSION->activegroup[$course->id]['aag'][0] = $group->id;
1375 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1377 // The user should still be listed when fetching just their group.
1378 $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id;
1379 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1383 public function x_test_count_submissions_for_team() {
1384 $this->resetAfterTest();
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 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1390 $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1391 groups_add_member($group, $student);
1393 $assign = $this->create_instance($course, [
1394 'assignsubmission_onlinetext_enabled' => 1,
1395 'teamsubmission' => 1,
1398 // Add a graded submission.
1399 $this->add_submission($student, $assign);
1403 // Simulate adding a grade.
1404 $this->setUser($teacher);
1405 $data = new stdClass();
1406 $data->grade = '50.0';
1407 $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
1409 // Simulate a submission.
1410 $this->setUser($this->extrastudents[1]);
1411 $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
1412 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1413 $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1414 $data = new stdClass();
1415 $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1416 'text' => 'Submission text',
1417 'format' => FORMAT_MOODLE);
1418 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1419 $plugin->save($submission, $data);
1421 // Simulate a submission.
1422 $this->setUser($this->extrastudents[2]);
1423 $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
1424 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1425 $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1426 $data = new stdClass();
1427 $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1428 'text' => 'Submission text',
1429 'format' => FORMAT_MOODLE);
1430 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1431 $plugin->save($submission, $data);
1433 // Simulate a submission.
1434 $this->setUser($this->extrastudents[3]);
1435 $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
1436 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1437 $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1438 $data = new stdClass();
1439 $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1440 'text' => 'Submission text',
1441 'format' => FORMAT_MOODLE);
1442 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1443 $plugin->save($submission, $data);
1445 // Simulate adding a grade.
1446 $this->setUser($teacher);
1447 $data = new stdClass();
1448 $data->grade = '50.0';
1449 $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1450 $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1452 // Create a new submission with status NEW.
1453 $this->setUser($this->extrastudents[4]);
1454 $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
1456 $this->assertEquals(2, $assign->count_grades());
1457 $this->assertEquals(4, $assign->count_submissions());
1458 $this->assertEquals(5, $assign->count_submissions(true));
1459 $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1460 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1463 public function test_get_grading_userid_list_only_active() {
1464 $this->resetAfterTest();
1466 $course = $this->getDataGenerator()->create_course();
1467 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1468 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1469 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1471 $this->setUser($teacher);
1473 $assign = $this->create_instance($course);
1474 $this->assertCount(1, $assign->testable_get_grading_userid_list());
1477 public function test_get_grading_userid_list_all() {
1478 $this->resetAfterTest();
1480 $course = $this->getDataGenerator()->create_course();
1481 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1482 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1483 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1485 $this->setUser($teacher);
1486 set_user_preference('grade_report_showonlyactiveenrol', false);
1488 $assign = $this->create_instance($course);
1489 $this->assertCount(2, $assign->testable_get_grading_userid_list());
1492 public function test_cron() {
1494 $this->resetAfterTest();
1496 // First run cron so there are no messages waiting to be sent (from other tests).
1500 $course = $this->getDataGenerator()->create_course();
1501 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1502 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1504 // Now create an assignment and add some feedback.
1505 $this->setUser($teacher);
1506 $assign = $this->create_instance($course, [
1507 'sendstudentnotifications' => 1,
1510 $this->add_submission($student, $assign);
1511 $this->submit_for_grading($student, $assign);
1512 $this->mark_submission($teacher, $assign, $student, 50.0);
1514 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1516 $sink = $this->redirectMessages();
1518 $messages = $sink->get_messages();
1520 $this->assertEquals(1, count($messages));
1521 $this->assertEquals(1, $messages[0]->notification);
1522 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1524 $customdata = json_decode($messages[0]->customdata);
1525 $this->assertEquals($assign->get_course_module()->id, $customdata->cmid);
1526 $this->assertEquals($assign->get_instance()->id, $customdata->instance);
1527 $this->assertEquals('feedbackavailable', $customdata->messagetype);
1528 $userpicture = new user_picture($teacher);
1529 $userpicture->size = 1; // Use f1 size.
1530 $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
1531 $this->assertEquals(0, $customdata->uniqueidforuser); // Not used in this case.
1532 $this->assertFalse($customdata->blindmarking);
1535 public function test_cron_without_notifications() {
1536 $this->resetAfterTest();
1538 // First run cron so there are no messages waiting to be sent (from other tests).
1542 $course = $this->getDataGenerator()->create_course();
1543 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1544 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1546 // Now create an assignment and add some feedback.
1547 $this->setUser($teacher);
1548 $assign = $this->create_instance($course, [
1549 'sendstudentnotifications' => 1,
1552 $this->add_submission($student, $assign);
1553 $this->submit_for_grading($student, $assign);
1554 $this->mark_submission($teacher, $assign, $student, 50.0, [
1555 'sendstudentnotifications' => 0,
1559 $sink = $this->redirectMessages();
1561 $messages = $sink->get_messages();
1563 $this->assertEquals(0, count($messages));
1566 public function test_cron_regraded() {
1567 $this->resetAfterTest();
1569 // First run cron so there are no messages waiting to be sent (from other tests).
1573 $course = $this->getDataGenerator()->create_course();
1574 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1575 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1577 // Now create an assignment and add some feedback.
1578 $this->setUser($teacher);
1579 $assign = $this->create_instance($course, [
1580 'sendstudentnotifications' => 1,
1583 $this->add_submission($student, $assign);
1584 $this->submit_for_grading($student, $assign);
1585 $this->mark_submission($teacher, $assign, $student, 50.0);
1587 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1592 $this->mark_submission($teacher, $assign, $student, 50.0);
1594 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1596 $sink = $this->redirectMessages();
1598 $messages = $sink->get_messages();
1600 $this->assertEquals(1, count($messages));
1601 $this->assertEquals(1, $messages[0]->notification);
1602 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1606 * Test delivery of grade notifications as controlled by marking workflow.
1608 public function test_markingworkflow_cron() {
1609 $this->resetAfterTest();
1611 // First run cron so there are no messages waiting to be sent (from other tests).
1615 $course = $this->getDataGenerator()->create_course();
1616 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1617 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1619 // Now create an assignment and add some feedback.
1620 $this->setUser($teacher);
1621 $assign = $this->create_instance($course, [
1622 'sendstudentnotifications' => 1,
1623 'markingworkflow' => 1,
1626 // Mark a submission but set the workflowstate to an unreleased state.
1627 // This should not trigger a notification.
1628 $this->add_submission($student, $assign);
1629 $this->submit_for_grading($student, $assign);
1630 $this->mark_submission($teacher, $assign, $student, 50.0, [
1631 'sendstudentnotifications' => 1,
1632 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1636 $sink = $this->redirectMessages();
1638 $messages = $sink->get_messages();
1640 $this->assertEquals(0, count($messages));
1642 // Transition to the released state.
1643 $this->setUser($teacher);
1644 $submission = $assign->get_user_submission($student->id, true);
1645 $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1646 $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1648 // Now run cron and see that one message was sent.
1650 $sink = $this->redirectMessages();
1651 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1653 $messages = $sink->get_messages();
1655 $this->assertEquals(1, count($messages));
1656 $this->assertEquals(1, $messages[0]->notification);
1657 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1660 public function test_cron_message_includes_courseid() {
1661 $this->resetAfterTest();
1663 // First run cron so there are no messages waiting to be sent (from other tests).
1667 $course = $this->getDataGenerator()->create_course();
1668 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1669 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1671 // Now create an assignment and add some feedback.
1672 $this->setUser($teacher);
1673 $assign = $this->create_instance($course, [
1674 'sendstudentnotifications' => 1,
1677 // Mark a submission but set the workflowstate to an unreleased state.
1678 // This should not trigger a notification.
1679 $this->add_submission($student, $assign);
1680 $this->submit_for_grading($student, $assign);
1681 $this->mark_submission($teacher, $assign, $student);
1682 phpunit_util::stop_message_redirection();
1684 // Now run cron and see that one message was sent.
1686 $this->preventResetByRollback();
1687 $sink = $this->redirectEvents();
1688 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1691 $events = $sink->get_events();
1692 $event = reset($events);
1693 $this->assertInstanceOf('\core\event\notification_sent', $event);
1694 $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1698 public function test_is_graded() {
1699 $this->resetAfterTest();
1701 $course = $this->getDataGenerator()->create_course();
1702 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1703 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1704 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1706 $assign = $this->create_instance($course);
1708 $this->add_submission($student, $assign);
1709 $this->submit_for_grading($student, $assign);
1710 $this->mark_submission($teacher, $assign, $student, 50.0);
1712 $this->setUser($teacher);
1713 $this->assertEquals(true, $assign->testable_is_graded($student->id));
1714 $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1717 public function test_can_grade() {
1720 $this->resetAfterTest();
1722 $course = $this->getDataGenerator()->create_course();
1723 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1724 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1726 $assign = $this->create_instance($course);
1728 $this->setUser($student);
1729 $this->assertEquals(false, $assign->can_grade());
1731 $this->setUser($teacher);
1732 $this->assertEquals(true, $assign->can_grade());
1734 // Test the viewgrades capability for other users.
1736 $this->assertTrue($assign->can_grade($teacher->id));
1737 $this->assertFalse($assign->can_grade($student->id));
1739 // Test the viewgrades capability - without mod/assign:grade.
1740 $this->setUser($student);
1742 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1743 assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1744 $this->assertEquals(false, $assign->can_grade());
1747 public function test_can_view_submission() {
1750 $this->resetAfterTest();
1752 $course = $this->getDataGenerator()->create_course();
1753 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1754 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1755 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1756 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1757 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1759 $assign = $this->create_instance($course);
1761 $this->setUser($student);
1762 $this->assertEquals(true, $assign->can_view_submission($student->id));
1763 $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1764 $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1766 $this->setUser($teacher);
1767 $this->assertEquals(true, $assign->can_view_submission($student->id));
1768 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1769 $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1770 $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1772 $this->setUser($editingteacher);
1773 $this->assertEquals(true, $assign->can_view_submission($student->id));
1774 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1775 $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1776 $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1778 // Test the viewgrades capability - without mod/assign:grade.
1779 $this->setUser($student);
1780 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1781 assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1782 $this->assertEquals(true, $assign->can_view_submission($student->id));
1783 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1784 $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1785 $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1788 public function test_update_submission() {
1789 $this->resetAfterTest();
1791 $course = $this->getDataGenerator()->create_course();
1792 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1793 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1795 $assign = $this->create_instance($course);
1797 $this->add_submission($student, $assign);
1798 $submission = $assign->get_user_submission($student->id, 0);
1799 $assign->testable_update_submission($submission, $student->id, true, true);
1801 $this->setUser($teacher);
1803 // Verify the gradebook update.
1804 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1806 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1807 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1810 public function test_update_submission_team() {
1811 $this->resetAfterTest();
1813 $course = $this->getDataGenerator()->create_course();
1814 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1815 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1817 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1818 groups_add_member($group, $student);
1820 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1821 groups_add_member($group, $otherstudent);
1823 $assign = $this->create_instance($course, [
1824 'teamsubmission' => 1,
1827 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1828 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1829 $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1831 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1832 $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1833 $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1835 $this->add_submission($student, $assign);
1836 $submission = $assign->get_group_submission($student->id, 0, true);
1837 $assign->testable_update_submission($submission, $student->id, true, true);
1839 // Verify the gradebook update for the student.
1840 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1842 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1843 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1845 // Verify the gradebook update for the other student.
1846 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1848 $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1849 $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1852 public function test_update_submission_suspended() {
1853 $this->resetAfterTest();
1855 $course = $this->getDataGenerator()->create_course();
1856 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1857 $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1859 $assign = $this->create_instance($course);
1861 $this->add_submission($student, $assign);
1862 $submission = $assign->get_user_submission($student->id, 0);
1863 $assign->testable_update_submission($submission, $student->id, true, false);
1865 $this->setUser($teacher);
1867 // Verify the gradebook update.
1868 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1870 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1871 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1874 public function test_update_submission_blind() {
1875 $this->resetAfterTest();
1877 $course = $this->getDataGenerator()->create_course();
1878 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1879 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1881 $assign = $this->create_instance($course, [
1882 'blindmarking' => 1,
1885 $this->add_submission($student, $assign);
1886 $submission = $assign->get_user_submission($student->id, 0);
1887 $assign->testable_update_submission($submission, $student->id, true, false);
1889 // Verify the gradebook update.
1890 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1892 // The usermodified is not set because this is blind marked.
1893 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1894 $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1897 public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
1900 $this->resetAfterTest();
1902 $course = $this->getDataGenerator()->create_course();
1903 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1904 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1906 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1907 groups_add_member($group, $student);
1909 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1910 groups_add_member($group, $otherstudent);
1912 $assign = $this->create_instance($course, [
1913 'teamsubmission' => 1,
1914 'assignsubmission_onlinetext_enabled' => 1,
1915 'submissiondrafts' => 1,
1916 'requireallteammemberssubmit' => 1,
1919 // Now verify group assignments.
1920 $this->setUser($teacher);
1921 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1923 // Add a submission.
1924 $this->add_submission($student, $assign);
1926 // Check we can see the submit button.
1927 $this->setUser($student);
1928 $output = $assign->view_student_summary($student, true);
1929 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1931 $submission = $assign->get_group_submission($student->id, 0, true);
1932 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1933 $assign->testable_update_submission($submission, $student->id, true, true);
1935 // Check that the student does not see "Submit" button.
1936 $output = $assign->view_student_summary($student, true);
1937 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1939 // Change to another user in the same group.
1940 $this->setUser($otherstudent);
1941 $output = $assign->view_student_summary($otherstudent, true);
1942 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1944 $submission = $assign->get_group_submission($otherstudent->id, 0, true);
1945 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1946 $assign->testable_update_submission($submission, $otherstudent->id, true, true);
1947 $output = $assign->view_student_summary($otherstudent, true);
1948 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1951 public function test_group_submissions_submit_for_marking() {
1954 $this->resetAfterTest();
1956 $course = $this->getDataGenerator()->create_course();
1957 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1958 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1960 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1961 groups_add_member($group, $student);
1963 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1964 groups_add_member($group, $otherstudent);
1966 // Now verify group assignments.
1967 $this->setUser($teacher);
1969 $assign = $this->create_instance($course, [
1970 'teamsubmission' => 1,
1971 'assignsubmission_onlinetext_enabled' => 1,
1972 'submissiondrafts' => 1,
1973 'requireallteammemberssubmit' => 0,
1974 'duedate' => $time - (2 * DAYSECS),
1976 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1978 // Add a submission.
1979 $this->add_submission($student, $assign);
1982 // Check we can see the submit button.
1983 $output = $assign->view_student_summary($student, true);
1984 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1985 $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
1986 $difftime = time() - $time;
1987 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
1989 $submission = $assign->get_group_submission($student->id, 0, true);
1990 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1991 $assign->testable_update_submission($submission, $student->id, true, true);
1993 // Check that the student does not see "Submit" button.
1994 $output = $assign->view_student_summary($student, true);
1995 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1997 // Change to another user in the same group.
1998 $this->setUser($otherstudent);
1999 $output = $assign->view_student_summary($otherstudent, true);
2000 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2002 // Check that time remaining is not overdue.
2003 $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2004 $difftime = time() - $time;
2005 $this->assertStringContainsString(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2007 $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2008 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2009 $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2010 $output = $assign->view_student_summary($otherstudent, true);
2011 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2014 public function test_submissions_open() {
2017 $this->resetAfterTest();
2019 $course = $this->getDataGenerator()->create_course();
2020 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2021 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2022 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2023 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2024 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
2026 $this->setAdminUser();
2029 $tomorrow = $now + DAYSECS;
2030 $oneweek = $now + WEEKSECS;
2031 $yesterday = $now - DAYSECS;
2033 $assign = $this->create_instance($course);
2034 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2036 $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
2037 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2039 $assign = $this->create_instance($course, ['duedate' => $yesterday]);
2040 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2042 $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
2043 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2045 $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2046 $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2048 $assign->testable_save_user_extension($student->id, $tomorrow);
2049 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2051 $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2052 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2054 $this->setUser($student);
2055 $submission = $assign->get_user_submission($student->id, true);
2056 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2057 $assign->testable_update_submission($submission, $student->id, true, false);
2059 $this->setUser($teacher);
2060 $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2063 public function test_get_graders() {
2066 $this->resetAfterTest();
2068 $course = $this->getDataGenerator()->create_course();
2069 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2070 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2071 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2073 $this->setAdminUser();
2075 // Create an assignment with no groups.
2076 $assign = $this->create_instance($course);
2077 $this->assertCount(2, $assign->testable_get_graders($student->id));
2080 public function test_get_graders_separate_groups() {
2083 $this->resetAfterTest();
2085 $course = $this->getDataGenerator()->create_course();
2086 $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2087 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2088 $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2089 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2090 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2091 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2093 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2094 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2095 groups_add_member($group1, $student);
2097 $this->setAdminUser();
2099 // Force create an assignment with SEPARATEGROUPS.
2100 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2101 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2103 $assign = $this->create_instance($course, [
2104 'groupingid' => $grouping->id,
2105 'groupmode' => SEPARATEGROUPS,
2108 $this->assertCount(4, $assign->testable_get_graders($student->id));
2110 // Note the second student is in a group that is not in the grouping.
2111 // This means that we get all graders that are not in a group in the grouping.
2112 $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2115 public function test_get_notified_users() {
2118 $this->resetAfterTest();
2120 $course = $this->getDataGenerator()->create_course();
2121 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2122 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2123 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2125 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2126 groups_add_member($group1, $teacher);
2128 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2129 groups_add_member($group1, $editingteacher);
2131 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2132 groups_add_member($group1, $student);
2134 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2135 $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2137 $capability = 'mod/assign:receivegradernotifications';
2138 $coursecontext = context_course::instance($course->id);
2139 $role = $DB->get_record('role', array('shortname' => 'teacher'));
2141 $this->setUser($teacher);
2143 // Create an assignment with no groups.
2144 $assign = $this->create_instance($course);
2146 $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2148 // Change nonediting teachers role to not receive grader notifications.
2149 assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2151 // Only the editing teachers will be returned.
2152 $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2154 // Note the second student is in a group that is not in the grouping.
2155 // This means that we get all graders that are not in a group in the grouping.
2156 $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2159 public function test_get_notified_users_in_grouping() {
2162 $this->resetAfterTest();
2164 $course = $this->getDataGenerator()->create_course();
2165 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2166 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2167 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2169 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2170 groups_add_member($group1, $teacher);
2172 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2173 groups_add_member($group1, $editingteacher);
2175 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2176 groups_add_member($group1, $student);
2178 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2179 $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2181 // Force create an assignment with SEPARATEGROUPS.
2182 $assign = $this->create_instance($course, [
2183 'groupingid' => $grouping->id,
2184 'groupmode' => SEPARATEGROUPS,
2187 // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2188 $this->setUser($student);
2189 $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2191 // Note the second student is in a group that is not in the grouping.
2192 // This means that we get all graders that are not in a group in the grouping.
2193 $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2195 // Change nonediting teachers role to not receive grader notifications.
2196 $capability = 'mod/assign:receivegradernotifications';
2197 $coursecontext = context_course::instance($course->id);
2198 $role = $DB->get_record('role', ['shortname' => 'teacher']);
2199 assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2201 // Only the editing teachers will be returned.
2202 $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2204 // Note the second student is in a group that is not in the grouping.
2205 // This means that we get all graders that are not in a group in the grouping.
2206 // Unfortunately there are no editing teachers who are not in a group.
2207 $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2210 public function test_group_members_only() {
2213 $this->resetAfterTest();
2215 $course = $this->getDataGenerator()->create_course();
2216 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2217 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2218 $this->getDataGenerator()->create_grouping_group([
2219 'groupid' => $group1->id,
2220 'groupingid' => $grouping->id,
2223 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2224 $this->getDataGenerator()->create_grouping_group([
2225 'groupid' => $group2->id,
2226 'groupingid' => $grouping->id,
2229 $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2231 // Add users in the following groups
2232 // - Teacher - Group 1.
2233 // - Student - Group 1.
2234 // - Student - Group 2.
2235 // - Student - Unrelated Group
2236 // - Student - No group.
2237 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2238 groups_add_member($group1, $teacher);
2240 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2241 groups_add_member($group1, $student);
2243 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2244 groups_add_member($group2, $otherstudent);
2246 $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2247 groups_add_member($group2, $otherstudent);
2249 $this->getDataGenerator()->create_and_enrol($course, 'student');
2251 $this->setAdminUser();
2253 $CFG->enableavailability = true;
2254 $assign = $this->create_instance($course, [], [
2255 'availability' => json_encode(
2256 \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2258 'groupingid' => $grouping->id,
2261 // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2262 // group, or the student in an unrelated group.
2263 $this->setUser($teacher);
2264 $participants = $assign->list_participants(0, true);
2265 $this->assertCount(2, $participants);
2266 $this->assertTrue(isset($participants[$student->id]));
2267 $this->assertTrue(isset($participants[$otherstudent->id]));
2270 public function test_get_uniqueid_for_user() {
2271 $this->resetAfterTest();
2273 $course = $this->getDataGenerator()->create_course();
2274 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2276 for ($i = 0; $i < 10; $i++) {
2277 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2278 $students[$student->id] = $student;
2281 $this->setUser($teacher);
2282 $assign = $this->create_instance($course);
2284 foreach ($students as $student) {
2285 $uniqueid = $assign->get_uniqueid_for_user($student->id);
2286 $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2290 public function test_show_student_summary() {
2293 $this->resetAfterTest();
2295 $course = $this->getDataGenerator()->create_course();
2296 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2297 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2298 $this->setUser($teacher);
2299 $assign = $this->create_instance($course);
2300 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2302 // No feedback should be available because this student has not been graded.
2303 $this->setUser($student);
2304 $output = $assign->view_student_summary($student, true);
2305 $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if there is no grade');
2307 // Simulate adding a grade.
2308 $this->add_submission($student, $assign);
2309 $this->submit_for_grading($student, $assign);
2310 $this->mark_submission($teacher, $assign, $student);
2312 // Now we should see the feedback.
2313 $this->setUser($student);
2314 $output = $assign->view_student_summary($student, true);
2315 $this->assertRegexp('/Feedback/', $output, 'Show feedback if there is a grade');
2317 // Now hide the grade in gradebook.
2318 $this->setUser($teacher);
2319 require_once($CFG->libdir.'/gradelib.php');
2320 $gradeitem = new grade_item(array(
2321 'itemtype' => 'mod',
2322 'itemmodule' => 'assign',
2323 'iteminstance' => $assign->get_instance()->id,
2324 'courseid' => $course->id));
2326 $gradeitem->set_hidden(1, false);
2328 // No feedback should be available because the grade is hidden.
2329 $this->setUser($student);
2330 $output = $assign->view_student_summary($student, true);
2331 $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2334 public function test_show_student_summary_with_feedback() {
2337 $this->resetAfterTest();
2339 $course = $this->getDataGenerator()->create_course();
2340 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2341 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2342 $this->setUser($teacher);
2343 $assign = $this->create_instance($course, [
2344 'assignfeedback_comments_enabled' => 1
2346 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2348 // No feedback should be available because this student has not been graded.
2349 $this->setUser($student);
2350 $output = $assign->view_student_summary($student, true);
2351 $this->assertNotRegexp('/Feedback/', $output);
2353 // Simulate adding a grade.
2354 $this->add_submission($student, $assign);
2355 $this->submit_for_grading($student, $assign);
2356 $this->mark_submission($teacher, $assign, $student, null, [
2357 'assignfeedbackcomments_editor' => [
2358 'text' => 'Tomato sauce',
2359 'format' => FORMAT_MOODLE,
2363 // Should have feedback but no grade.
2364 $this->setUser($student);
2365 $output = $assign->view_student_summary($student, true);
2366 $this->assertRegexp('/Feedback/', $output);
2367 $this->assertRegexp('/Tomato sauce/', $output);
2368 $this->assertNotRegexp('/Grade/', $output, 'Do not show grade when there is no grade.');
2369 $this->assertNotRegexp('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2372 $this->mark_submission($teacher, $assign, $student, 50.0, [
2373 'assignfeedbackcomments_editor' => [
2374 'text' => 'Bechamel sauce',
2375 'format' => FORMAT_MOODLE,
2379 // Should have feedback but no grade.
2380 $this->setUser($student);
2381 $output = $assign->view_student_summary($student, true);
2382 $this->assertNotRegexp('/Tomato sauce/', $output);
2383 $this->assertRegexp('/Bechamel sauce/', $output);
2384 $this->assertRegexp('/Grade/', $output);
2385 $this->assertRegexp('/Graded on/', $output);
2387 // Now hide the grade in gradebook.
2388 $this->setUser($teacher);
2389 $gradeitem = new grade_item(array(
2390 'itemtype' => 'mod',
2391 'itemmodule' => 'assign',
2392 'iteminstance' => $assign->get_instance()->id,
2393 'courseid' => $course->id));
2395 $gradeitem->set_hidden(1, false);
2397 // No feedback should be available because the grade is hidden.
2398 $this->setUser($student);
2399 $output = $assign->view_student_summary($student, true);
2400 $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2404 * Test reopen behavior when in "Manual" mode.
2406 public function test_attempt_reopen_method_manual() {
2409 $this->resetAfterTest();
2410 $course = $this->getDataGenerator()->create_course();
2411 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2412 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2414 $assign = $this->create_instance($course, [
2415 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2417 'submissiondrafts' => 1,
2418 'assignsubmission_onlinetext_enabled' => 1,
2420 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2422 // Student should be able to see an add submission button.
2423 $this->setUser($student);
2424 $output = $assign->view_student_summary($student, true);
2425 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2427 // Add a submission.
2428 $this->add_submission($student, $assign);
2429 $this->submit_for_grading($student, $assign);
2431 // Verify the student cannot make changes to the submission.
2432 $output = $assign->view_student_summary($student, true);
2433 $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2435 // Mark the submission.
2436 $this->mark_submission($teacher, $assign, $student);
2438 // Check the student can see the grade.
2439 $this->setUser($student);
2440 $output = $assign->view_student_summary($student, true);
2441 $this->assertNotEquals(false, strpos($output, '50.0'));
2443 // Allow the student another attempt.
2444 $teacher->ignoresesskey = true;
2445 $this->setUser($teacher);
2446 $result = $assign->testable_process_add_attempt($student->id);
2447 $this->assertEquals(true, $result);
2449 // Check that the previous attempt is now in the submission history table.
2450 $this->setUser($student);
2451 $output = $assign->view_student_summary($student, true);
2452 // Need a better check.
2453 $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2455 // Check that the student now has a button for Add a new attempt".
2456 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2457 // Check that the student now does not have a button for Submit.
2458 $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2460 // Check that the student now has a submission history.
2461 $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2463 $this->setUser($teacher);
2464 // Check that the grading table loads correctly and contains this user.
2465 // This is also testing that we do not get duplicate rows in the grading table.
2466 $gradingtable = new assign_grading_table($assign, 100, '', 0, true);
2467 $output = $assign->get_renderer()->render($gradingtable);
2468 $this->assertEquals(true, strpos($output, $student->lastname));
2470 // Should be 1 not 2.
2471 $this->assertEquals(1, $assign->count_submissions());
2472 $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2473 $this->assertEquals(0, $assign->count_submissions_need_grading());
2474 $this->assertEquals(1, $assign->count_grades());
2476 // Change max attempts to unlimited.
2477 $formdata = clone($assign->get_instance());
2478 $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2479 $formdata->instance = $formdata->id;
2480 $assign->update_instance($formdata);
2482 // Mark the submission again.
2483 $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2485 // Check the grade exists.
2486 $this->setUser($teacher);
2487 $grades = $assign->get_user_grades_for_gradebook($student->id);
2488 $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2490 // Check we can reopen still.
2491 $result = $assign->testable_process_add_attempt($student->id);
2492 $this->assertEquals(true, $result);
2494 // Should no longer have a grade because there is no grade for the latest attempt.
2495 $grades = $assign->get_user_grades_for_gradebook($student->id);
2496 $this->assertEmpty($grades);
2500 * Test reopen behavior when in "Reopen until pass" mode.
2502 public function test_attempt_reopen_method_untilpass() {
2505 $this->resetAfterTest();
2506 $course = $this->getDataGenerator()->create_course();
2507 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2508 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2510 $assign = $this->create_instance($course, [
2511 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2513 'submissiondrafts' => 1,
2514 'assignsubmission_onlinetext_enabled' => 1,
2516 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2518 // Set grade to pass to 80.
2519 $gradeitem = $assign->get_grade_item();
2520 $gradeitem->gradepass = '80.0';
2521 $gradeitem->update();
2523 // Student should be able to see an add submission button.
2524 $this->setUser($student);
2525 $output = $assign->view_student_summary($student, true);
2526 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2528 // Add a submission.
2529 $this->add_submission($student, $assign);
2530 $this->submit_for_grading($student, $assign);
2532 // Verify the student cannot make a new attempt.
2533 $output = $assign->view_student_summary($student, true);
2534 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2536 // Mark the submission as non-passing.
2537 $this->mark_submission($teacher, $assign, $student, 50.0);
2539 // Check the student can see the grade.
2540 $this->setUser($student);
2541 $output = $assign->view_student_summary($student, true);
2542 $this->assertNotEquals(false, strpos($output, '50.0'));
2544 // Check that the student now has a button for Add a new attempt.
2545 $output = $assign->view_student_summary($student, true);
2546 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2548 // Check that the student now does not have a button for Submit.
2549 $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2551 // Check that the student now has a submission history.
2552 $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2554 // Add a second submission.
2555 $this->add_submission($student, $assign);
2556 $this->submit_for_grading($student, $assign);
2558 // Mark the submission as passing.
2559 $this->mark_submission($teacher, $assign, $student, 80.0);
2561 // Check that the student does not have a button for Add a new attempt.
2562 $this->setUser($student);
2563 $output = $assign->view_student_summary($student, true);
2564 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2566 // Re-mark the submission as not passing.
2567 $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2569 // Check that the student now has a button for Add a new attempt.
2570 $this->setUser($student);
2571 $output = $assign->view_student_summary($student, true);
2572 $this->assertRegexp('/' . get_string('addnewattempt', 'assign') . '/', $output);
2573 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2576 public function test_attempt_reopen_method_untilpass_passing() {
2579 $this->resetAfterTest();
2580 $course = $this->getDataGenerator()->create_course();
2581 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2582 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2584 $assign = $this->create_instance($course, [
2585 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2587 'submissiondrafts' => 1,
2588 'assignsubmission_onlinetext_enabled' => 1,
2590 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2592 // Set grade to pass to 80.
2593 $gradeitem = $assign->get_grade_item();
2594 $gradeitem->gradepass = '80.0';
2595 $gradeitem->update();
2597 // Student should be able to see an add submission button.
2598 $this->setUser($student);
2599 $output = $assign->view_student_summary($student, true);
2600 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2602 // Add a submission as a student.
2603 $this->add_submission($student, $assign);
2604 $this->submit_for_grading($student, $assign);
2606 // Mark the submission as passing.
2607 $this->mark_submission($teacher, $assign, $student, 100.0);
2609 // Check the student can see the grade.
2610 $this->setUser($student);
2611 $output = $assign->view_student_summary($student, true);
2612 $this->assertNotEquals(false, strpos($output, '100.0'));
2614 // Check that the student does not have a button for Add a new attempt.
2615 $output = $assign->view_student_summary($student, true);
2616 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2619 public function test_attempt_reopen_method_untilpass_no_passing_requirement() {
2622 $this->resetAfterTest();
2623 $course = $this->getDataGenerator()->create_course();
2624 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2625 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2627 $assign = $this->create_instance($course, [
2628 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2630 'submissiondrafts' => 1,
2631 'assignsubmission_onlinetext_enabled' => 1,
2633 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2635 // Set grade to pass to 0, so that no attempts should reopen.
2636 $gradeitem = $assign->get_grade_item();
2637 $gradeitem->gradepass = '0';
2638 $gradeitem->update();
2640 // Student should be able to see an add submission button.
2641 $this->setUser($student);
2642 $output = $assign->view_student_summary($student, true);
2643 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2645 // Add a submission.
2646 $this->add_submission($student, $assign);
2647 $this->submit_for_grading($student, $assign);
2649 // Mark the submission with any grade.
2650 $this->mark_submission($teacher, $assign, $student, 0.0);
2652 // Check the student can see the grade.
2653 $this->setUser($student);
2654 $output = $assign->view_student_summary($student, true);
2655 $this->assertNotEquals(false, strpos($output, '0.0'));
2657 // Check that the student does not have a button for Add a new attempt.
2658 $output = $assign->view_student_summary($student, true);
2659 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2663 * Test student visibility for each stage of the marking workflow.
2665 public function test_markingworkflow() {
2668 $this->resetAfterTest();
2669 $course = $this->getDataGenerator()->create_course();
2670 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2671 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2673 $assign = $this->create_instance($course, [
2674 'markingworkflow' => 1,
2677 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2679 // Mark the submission and set to notmarked.
2680 $this->mark_submission($teacher, $assign, $student, 50.0, [
2681 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2684 // Check the student can't see the grade.
2685 $this->setUser($student);
2686 $output = $assign->view_student_summary($student, true);
2687 $this->assertEquals(false, strpos($output, '50.0'));
2689 // Make sure the grade isn't pushed to the gradebook.
2690 $grades = $assign->get_user_grades_for_gradebook($student->id);
2691 $this->assertEmpty($grades);
2693 // Mark the submission and set to inmarking.
2694 $this->mark_submission($teacher, $assign, $student, 50.0, [
2695 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2698 // Check the student can't see the grade.
2699 $this->setUser($student);
2700 $output = $assign->view_student_summary($student, true);
2701 $this->assertEquals(false, strpos($output, '50.0'));
2703 // Make sure the grade isn't pushed to the gradebook.
2704 $grades = $assign->get_user_grades_for_gradebook($student->id);
2705 $this->assertEmpty($grades);
2707 // Mark the submission and set to readyforreview.
2708 $this->mark_submission($teacher, $assign, $student, 50.0, [
2709 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2712 // Check the student can't see the grade.
2713 $this->setUser($student);
2714 $output = $assign->view_student_summary($student, true);
2715 $this->assertEquals(false, strpos($output, '50.0'));
2717 // Make sure the grade isn't pushed to the gradebook.
2718 $grades = $assign->get_user_grades_for_gradebook($student->id);
2719 $this->assertEmpty($grades);
2721 // Mark the submission and set to inreview.
2722 $this->mark_submission($teacher, $assign, $student, 50.0, [
2723 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2726 // Check the student can't see the grade.
2727 $this->setUser($student);
2728 $output = $assign->view_student_summary($student, true);
2729 $this->assertEquals(false, strpos($output, '50.0'));
2731 // Make sure the grade isn't pushed to the gradebook.
2732 $grades = $assign->get_user_grades_for_gradebook($student->id);
2733 $this->assertEmpty($grades);
2735 // Mark the submission and set to readyforrelease.
2736 $this->mark_submission($teacher, $assign, $student, 50.0, [
2737 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
2740 // Check the student can't see the grade.
2741 $this->setUser($student);
2742 $output = $assign->view_student_summary($student, true);
2743 $this->assertEquals(false, strpos($output, '50.0'));
2745 // Make sure the grade isn't pushed to the gradebook.
2746 $grades = $assign->get_user_grades_for_gradebook($student->id);
2747 $this->assertEmpty($grades);
2749 // Mark the submission and set to released.
2750 $this->mark_submission($teacher, $assign, $student, 50.0, [
2751 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
2754 // Check the student can see the grade.
2755 $this->setUser($student);
2756 $output = $assign->view_student_summary($student, true);
2757 $this->assertNotEquals(false, strpos($output, '50.0'));
2759 // Make sure the grade is pushed to the gradebook.
2760 $grades = $assign->get_user_grades_for_gradebook($student->id);
2761 $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
2765 * Test that a student allocated a specific marker is only shown to that marker.
2767 public function test_markerallocation() {
2770 $this->resetAfterTest();
2771 $course = $this->getDataGenerator()->create_course();
2772 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2773 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2774 $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2776 $assign = $this->create_instance($course, [
2777 'markingworkflow' => 1,
2778 'markingallocation' => 1
2781 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2783 // Allocate marker to submission.
2784 $this->mark_submission($teacher, $assign, $student, null, [
2785 'allocatedmarker' => $teacher->id,
2788 // Check the allocated marker can view the submission.
2789 $this->setUser($teacher);
2790 $users = $assign->list_participants(0, true);
2791 $this->assertEquals(1, count($users));
2792 $this->assertTrue(isset($users[$student->id]));
2794 $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2795 $context = context_module::instance($cm->id);
2796 $assign = new mod_assign_testable_assign($context, $cm, $course);
2798 // Check that other teachers can't view this submission.
2799 $this->setUser($otherteacher);
2800 $users = $assign->list_participants(0, true);
2801 $this->assertEquals(0, count($users));
2805 * Ensure that a teacher cannot submit for students as standard.
2807 public function test_teacher_submit_for_student() {
2810 $this->resetAfterTest();
2811 $course = $this->getDataGenerator()->create_course();
2812 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2813 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2815 $assign = $this->create_instance($course, [
2816 'assignsubmission_onlinetext_enabled' => 1,
2817 'submissiondrafts' => 1,
2820 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2822 // Add a submission but do not submit.
2823 $this->add_submission($student, $assign, 'Student submission text');
2825 $this->setUser($student);
2826 $output = $assign->view_student_summary($student, true);
2827 $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2829 // Check that a teacher can not edit the submission as they do not have the capability.
2830 $this->setUser($teacher);
2831 $this->expectException('moodle_exception');
2832 $this->expectExceptionMessage('error/nopermission');
2833 $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2837 * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
2839 public function test_teacher_submit_for_student_with_capability() {
2842 $this->resetAfterTest();
2843 $course = $this->getDataGenerator()->create_course();
2844 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2845 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2846 $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2848 $assign = $this->create_instance($course, [
2849 'assignsubmission_onlinetext_enabled' => 1,
2850 'submissiondrafts' => 1,
2853 // Add the required capability.
2854 $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2855 assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2856 role_assign($roleid, $teacher->id, $assign->get_context()->id);
2857 accesslib_clear_all_caches_for_unit_testing();
2859 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2861 // Add a submission but do not submit.
2862 $this->add_submission($student, $assign, 'Student submission text');
2864 $this->setUser($student);
2865 $output = $assign->view_student_summary($student, true);
2866 $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2868 // Check that a teacher can edit the submission.
2869 $this->setUser($teacher);
2870 $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2872 $this->setUser($student);
2873 $output = $assign->view_student_summary($student, true);
2874 $this->assertStringNotContainsString('Student submission text', $output, 'Contains student submission text');
2875 $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains teacher edited submission text');
2877 // Check that the teacher can submit the students work.
2878 $this->setUser($teacher);
2879 $this->submit_for_grading($student, $assign, [], false);
2881 // Revert to draft so the student can edit it.
2882 $assign->revert_to_draft($student->id);
2884 $this->setUser($student);
2886 // Check that the submission text was saved.
2887 $output = $assign->view_student_summary($student, true);
2888 $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains student submission text');
2890 // Check that the student can submit their work.
2891 $this->submit_for_grading($student, $assign, []);
2893 $output = $assign->view_student_summary($student, true);
2894 $this->assertStringNotContainsString(get_string('addsubmission', 'assign'), $output);
2896 // An editing teacher without the extra role should still be able to revert to draft.
2897 $this->setUser($otherteacher);
2899 // Revert to draft so the submission is editable.
2900 $assign->revert_to_draft($student->id);
2904 * Ensure that disabling submit after the cutoff date works as expected.
2906 public function test_disable_submit_after_cutoff_date() {
2909 $this->resetAfterTest();
2910 $course = $this->getDataGenerator()->create_course();
2911 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2914 $tomorrow = $now + DAYSECS;
2915 $lastweek = $now - (7 * DAYSECS);
2916 $yesterday = $now - DAYSECS;
2918 $this->setAdminUser();
2919 $assign = $this->create_instance($course, [
2920 'duedate' => $yesterday,
2921 'cutoffdate' => $tomorrow,
2922 'assignsubmission_onlinetext_enabled' => 1,
2925 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2927 // Student should be able to see an add submission button.
2928 $this->setUser($student);
2929 $output = $assign->view_student_summary($student, true);
2930 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2932 // Add a submission but don't submit now.
2933 $this->add_submission($student, $assign);
2935 // Create another instance with cut-off and due-date already passed.
2936 $this->setAdminUser();
2937 $assign = $this->create_instance($course, [
2938 'duedate' => $lastweek,
2939 'cutoffdate' => $yesterday,
2940 'assignsubmission_onlinetext_enabled' => 1,
2943 $this->setUser($student);
2944 $output = $assign->view_student_summary($student, true);
2945 $this->assertStringNotContainsString($output, get_string('editsubmission', 'assign'),
2946 'Should not be able to edit after cutoff date.');
2947 $this->assertStringNotContainsString($output, get_string('submitassignment', 'assign'),
2948 'Should not be able to submit after cutoff date.');
2952 * Testing for submission comment plugin settings.
2954 * @dataProvider submission_plugin_settings_provider
2955 * @param bool $globalenabled
2956 * @param array $instanceconfig
2957 * @param bool $isenabled
2959 public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled) {
2962 $this->resetAfterTest();
2963 $course = $this->getDataGenerator()->create_course();
2965 $CFG->usecomments = $globalenabled;
2966 $assign = $this->create_instance($course, $instanceconfig);
2967 $plugin = $assign->get_submission_plugin_by_type('comments');
2968 $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
2971 public function submission_plugin_settings_provider() {
2973 'CFG->usecomments true, empty config => Enabled by default' => [
2978 'CFG->usecomments true, config enabled => Comments enabled' => [
2981 'assignsubmission_comments_enabled' => 1,
2985 'CFG->usecomments true, config idisabled => Comments enabled' => [
2988 'assignsubmission_comments_enabled' => 0,
2992 'CFG->usecomments false, empty config => Disabled by default' => [
2997 'CFG->usecomments false, config enabled => Comments disabled' => [
3000 'assignsubmission_comments_enabled' => 1,
3004 'CFG->usecomments false, config disabled => Comments disabled' => [
3007 'assignsubmission_comments_enabled' => 0,
3015 * Testing for comment inline settings
3017 public function test_feedback_comment_commentinline() {
3020 $this->resetAfterTest();
3021 $course = $this->getDataGenerator()->create_course();
3022 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3023 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3025 $sourcetext = "Hello!
3027 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3029 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3030 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3032 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
3033 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
3034 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
3035 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
3036 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
3037 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3040 $this->setUser($teacher);
3041 $assign = $this->create_instance($course, [
3042 'assignsubmission_onlinetext_enabled' => 1,
3043 'assignfeedback_comments_enabled' => 1,
3044 'assignfeedback_comments_commentinline' => 1,
3047 $this->setUser($student);
3049 // Add a submission but don't submit now.
3050 $this->add_submission($student, $assign, $sourcetext);
3052 $this->setUser($teacher);
3054 $data = new stdClass();
3055 require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3057 'userid' => $student->id,
3060 'useridlistid' => $assign->get_useridlist_key_id(),
3061 'attemptnumber' => 0,
3063 $formparams = array($assign, $data, $pagination);
3064 $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
3066 // We need to get the URL these will be transformed to.
3067 $context = context_user::instance($USER->id);
3068 $itemid = $data->assignfeedbackcomments_editor['itemid'];
3069 $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid;
3071 // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
3072 $filteredtext = "Hello!
3074 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3076 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3077 Plugin url outside a tag: $url/logo-240x60.gif
3079 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3080 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3081 Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" />
3082 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" />
3083 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a>
3084 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3087 $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3091 * Testing for feedback comment plugin settings.
3093 * @dataProvider feedback_plugin_settings_provider
3094 * @param array $instanceconfig
3095 * @param bool $isenabled
3097 public function test_feedback_plugin_settings($instanceconfig, $isenabled) {
3098 $this->resetAfterTest();
3099 $course = $this->getDataGenerator()->create_course();
3101 $assign = $this->create_instance($course, $instanceconfig);
3102 $plugin = $assign->get_feedback_plugin_by_type('comments');
3103 $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3106 public function feedback_plugin_settings_provider() {
3108 'No configuration => disabled' => [
3112 'Actively disabled' => [
3114 'assignfeedback_comments_enabled' => 0,
3118 'Actively enabled' => [
3120 'assignfeedback_comments_enabled' => 1,
3128 * Testing if gradebook feedback plugin is enabled.
3130 public function test_is_gradebook_feedback_enabled() {
3131 $this->resetAfterTest();
3132 $course = $this->getDataGenerator()->create_course();
3133 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3134 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3136 $adminconfig = get_config('assign');
3137 $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3139 // Create assignment with gradebook feedback enabled and grade = 0.
3140 $assign = $this->create_instance($course, [
3141 "{$gradebookplugin}_enabled" => 1,
3145 // Get gradebook feedback plugin.
3146 $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3147 $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3148 $this->assertEquals(1, $plugin->is_enabled('enabled'));
3149 $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3153 * Testing if gradebook feedback plugin is disabled.
3155 public function test_is_gradebook_feedback_disabled() {
3156 $this->resetAfterTest();
3157 $course = $this->getDataGenerator()->create_course();
3158 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3159 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3161 $adminconfig = get_config('assign');
3162 $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3164 // Create assignment with gradebook feedback disabled and grade = 0.
3165 $assign = $this->create_instance($course, [
3166 "{$gradebookplugin}_enabled" => 0,
3170 $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3171 $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3172 $this->assertEquals(0, $plugin->is_enabled('enabled'));
3176 * Testing can_edit_submission.
3178 public function test_can_edit_submission() {
3179 $this->resetAfterTest();
3180 $course = $this->getDataGenerator()->create_course();
3181 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3182 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3183 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3185 $assign = $this->create_instance($course, [
3186 'assignsubmission_onlinetext_enabled' => 1,
3187 'submissiondrafts' => 1,
3190 // Check student can edit their own submission.
3191 $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3193 // Check student cannot edit others submission.
3194 $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3196 // Check teacher cannot (by default) edit a students submission.
3197 $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3201 * Testing can_edit_submission with the editothersubmission capability.
3203 public function test_can_edit_submission_with_editothersubmission() {
3204 $this->resetAfterTest();
3205 $course = $this->getDataGenerator()->create_course();
3206 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3207 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3208 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3210 $assign = $this->create_instance($course, [
3211 'assignsubmission_onlinetext_enabled' => 1,
3212 'submissiondrafts' => 1,
3215 // Add the required capability to edit a student submission.
3216 $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3217 assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3218 role_assign($roleid, $teacher->id, $assign->get_context()->id);
3219 accesslib_clear_all_caches_for_unit_testing();
3221 // Check student can edit their own submission.
3222 $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3224 // Check student cannot edit others submission.
3225 $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3227 // Retest - should now have access.
3228 $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3232 * Testing can_edit_submission
3234 public function test_can_edit_submission_separategroups() {
3235 $this->resetAfterTest();
3236 $course = $this->getDataGenerator()->create_course();
3237 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3239 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3240 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3241 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3242 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3244 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3245 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3246 groups_assign_grouping($grouping->id, $group1->id);
3247 groups_add_member($group1, $student1);
3248 groups_add_member($group1, $student2);
3250 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3251 groups_assign_grouping($grouping->id, $group2->id);
3252 groups_add_member($group2, $student3);
3253 groups_add_member($group2, $student4);
3255 $assign = $this->create_instance($course, [
3256 'assignsubmission_onlinetext_enabled' => 1,
3257 'submissiondrafts' => 1,
3258 'groupingid' => $grouping->id,
3259 'groupmode' => SEPARATEGROUPS,
3262 // Verify a student does not have the ability to edit submissions for other users.
3263 $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3264 $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id));
3265 $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3266 $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3270 * Testing can_edit_submission
3272 public function test_can_edit_submission_separategroups_with_editothersubmission() {
3273 $this->resetAfterTest();
3274 $course = $this->getDataGenerator()->create_course();
3275 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3277 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3278 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3279 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3280 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3282 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3283 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3284 groups_assign_grouping($grouping->id, $group1->id);
3285 groups_add_member($group1, $student1);
3286 groups_add_member($group1, $student2);
3288 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3289 groups_assign_grouping($grouping->id, $group2->id);
3290 groups_add_member($group2, $student3);
3291 groups_add_member($group2, $student4);
3293 $assign = $this->create_instance($course, [
3294 'assignsubmission_onlinetext_enabled' => 1,
3295 'submissiondrafts' => 1,
3296 'groupingid' => $grouping->id,
3297 'groupmode' => SEPARATEGROUPS,
3300 // Add the capability to the new assignment for student 1.
3301 $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3302 assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3303 role_assign($roleid, $student1->id, $assign->get_context()->id);
3304 accesslib_clear_all_caches_for_unit_testing();
3306 // Verify student1 has the ability to edit submissions for other users in their group, but not other groups.
3307 $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3308 $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id));
3309 $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3310 $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));