MDL-66147 mod_assign: Change assign to return calculated instance data
[moodle.git] / mod / assign / tests / locallib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Unit tests for (some of) mod/assign/locallib.php.
19  *
20  * @package    mod_assign
21  * @category   phpunit
22  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
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');
34 /**
35  * Unit tests for (some of) mod/assign/locallib.php.
36  *
37  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
38  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
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() {
46         global $PAGE;
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());
57     }
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');
70         }
71     }
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');
84         }
85     }
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;
137     }
139     /**
140      * Data provider for test_get_assign_perpage
141      *
142      * @return array Provider data
143      */
144     public function get_assign_perpage_provider() {
145         return array(
146             array(
147                 'maxperpage' => -1,
148                 'userprefs' => array(
149                     -1 => -1,
150                     10 => 10,
151                     20 => 20,
152                     50 => 50,
153                 ),
154             ),
155             array(
156                 'maxperpage' => 15,
157                 'userprefs' => array(
158                     -1 => 15,
159                     10 => 10,
160                     20 => 15,
161                     50 => 15,
162                 ),
163             ),
164         );
165     }
167     /**
168      * Test maxperpage
169      *
170      * @dataProvider get_assign_perpage_provider
171      * @param integer $maxperpage site config value
172      * @param array $userprefs Array of user preferences and expected page sizes
173      */
174     public function test_get_assign_perpage($maxperpage, $userprefs) {
175         $this->resetAfterTest();
176         $course = $this->getDataGenerator()->create_course();
177         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
178         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
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());
189         }
190     }
192     /**
193      * Test filter by requires grading.
194      *
195      * This is specifically checking an assignment with no grade to make sure we do not
196      * get an exception thrown when rendering the grading table for this type of assignment.
197      */
198     public function test_gradingtable_filter_by_requiresgrading_no_grade() {
199         global $PAGE;
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
210             ]);
212         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
213             'id' => $assign->get_course_module()->id,
214             'action' => 'grading',
215         )));
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->assertContains(get_string('nothingtodisplay'), $output);
223     }
226     /**
227      * Test submissions with extension date.
228      */
229     public function test_gradingtable_extension_due_date() {
230         global $PAGE;
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);
239         $time = time();
240         $assign = $this->create_instance($course, [
241                 'assignsubmission_onlinetext_enabled' => 1,
242                 'duedate' => time() - (4 * DAYSECS),
243             ]);
244         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
245             'id' => $assign->get_course_module()->id,
246             'action' => 'grading',
247         )));
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->assertContains(get_string('submissionstatus_', 'assign'), $output);
253         $this->assertContains(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->assertContains(get_string('submissionstatus_', 'assign'), $output);
261         $this->assertContains(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,
273         ];
274         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
275         $plugin->save($submission, $data);
277         // Verify output.
278         $this->setUser($teacher);
279         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
280         $output = $assign->get_renderer()->render($gradingtable);
281         $this->assertContains(get_string('submissionstatus_submitted', 'assign'), $output);
282         $this->assertContains(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
283     }
285     /**
286      * Test that late submissions with extension date calculate correctly.
287      */
288     public function test_gradingtable_extension_date_calculation_for_lateness() {
289         global $PAGE;
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);
298         $time = time();
299         $assign = $this->create_instance($course, [
300                 'assignsubmission_onlinetext_enabled' => 1,
301                 'duedate' => time() - (4 * DAYSECS),
302             ]);
303         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
304             'id' => $assign->get_course_module()->id,
305             'action' => 'grading',
306         )));
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->assertContains(get_string('submissionstatus_', 'assign'), $output);
312         $difftime = time() - $time;
313         $this->assertContains(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->assertContains(get_string('submissionstatus_', 'assign'), $output);
320         $this->assertContains(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
321         $difftime = time() - $time;
322         $this->assertContains(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,
334         ];
335         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
336         $plugin->save($submission, $data);
337         $submittedtime = time();
339         // Verify output.
340         $this->setUser($teacher);
341         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
342         $output = $assign->get_renderer()->render($gradingtable);
343         $this->assertContains(get_string('submissionstatus_submitted', 'assign'), $output);
344         $this->assertContains(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
346         $difftime = $submittedtime - $time;
347         $this->assertContains(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
348     }
350     public function test_gradingtable_status_rendering() {
351         global $PAGE;
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);
360         $time = time();
361         $assign = $this->create_instance($course, [
362             'assignsubmission_onlinetext_enabled' => 1,
363             'duedate' => $time - (4 * DAYSECS),
364          ]);
365         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
366             'id' => $assign->get_course_module()->id,
367             'action' => 'grading',
368         )));
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->assertContains(get_string('submissionstatus_', 'assign'), $output);
374         $difftime = time() - $time;
375         $this->assertContains(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();
384         // Verify output.
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->assertContains(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"])'));
395     }
397     /**
398      * Check that group submission information is rendered correctly in the
399      * grading table.
400      */
401     public function test_gradingtable_group_submissions_rendering() {
402         global $PAGE;
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);
411         $students = [];
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,
436         ]);
437         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
438             'id' => $assign->get_course_module()->id,
439             'action' => 'grading',
440         )));
442         // Add a submission.
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,
449         ];
450         $notices = array();
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);
457         // Check output.
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);
465         // Check status.
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"])')));
473         // Check group.
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)'));
484     }
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,
502             ]);
503         $this->assertEquals(false, $assign->testable_show_intro());
504         $yesterday = time() - DAYSECS;
505         $assign = $this->create_instance($course, [
506                 'alwaysshowdescription' => 0,
507                 'allowsubmissionsfromdate' => $yesterday,
508             ]);
509         $this->assertEquals(true, $assign->testable_show_intro());
510     }
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);
542         // Now test again.
543         $this->assertEquals(true, $assign->has_submissions_or_grades());
544     }
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));
570     }
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());
593     }
595     public function test_reset_userdata() {
596         global $DB;
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');
603         $now = time();
604         $this->setUser($teacher);
605         $assign = $this->create_instance($course, [
606                 'assignsubmission_onlinetext_enabled' => 1,
607                 'duedate' => $now,
608             ]);
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,
652                 'duedate' => $now,
653             ]);
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);
671     }
673     public function test_plugin_settings() {
674         global $DB;
676         $this->resetAfterTest();
678         $course = $this->getDataGenerator()->create_course();
679         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
681         $now = time();
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,
687             ]);
689         $plugin = $assign->get_submission_plugin_by_type('file');
690         $this->assertEquals('12', $plugin->get_config('maxfilesubmissions'));
691     }
693     public function test_update_calendar() {
694         global $DB;
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.
717         $now = time();
718         $assign = $this->create_instance($course, [
719                 'duedate' => $now,
720                 'intro' => $fakearealink1,
721                 'introformat' => FORMAT_HTML
722             ]);
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',
747             ]);
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,
754             ]);
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.
763         assign::cron();
764         $event = $DB->get_record('event', $params);
766         $this->assertContains('Some text', $event->description);
768     }
770     public function test_update_instance() {
771         global $DB;
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]);
781         $now = time();
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);
791     }
793     public function test_cannot_submit_empty() {
794         global $PAGE;
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->assertNotContains(get_string('submitassignment', 'assign'), $output, 'Can submit empty offline assignment');
809     }
811     public function test_cannot_submit_empty_no_submission() {
812         global $PAGE;
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,
822         ]);
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->assertNotContains(get_string('submitassignment', 'assign'), $output, 'Cannot submit empty onlinetext assignment');
830     }
832     public function test_can_submit_with_submission() {
833         global $PAGE;
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,
843         ]);
845         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
847         // Add a draft.
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->assertContains(get_string('submitassignment', 'assign'), $output, 'Can submit non empty onlinetext assignment');
854     }
856     /**
857      * Test new_submission_empty
858      *
859      * We only test combinations of plugins here. Individual plugins are tested
860      * in their respective test files.
861      *
862      * @dataProvider test_new_submission_empty_testcases
863      * @param string $data The file submission data
864      * @param bool $expected The expected return value
865      */
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,
877             ]);
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']);
887         }
889         if ($data['onlinetext']) {
890             $submission->onlinetext_editor = ['text' => $data['onlinetext']];
891         }
893         $result = $assign->new_submission_empty($submission);
894         $this->assertTrue($result === $expected);
895     }
897     /**
898      * Dataprovider for the test_new_submission_empty testcase
899      *
900      * @return array of testcases
901      */
902     public function test_new_submission_empty_testcases() {
903         return [
904             'With file and onlinetext' => [
905                 [
906                     'file' => [
907                         'component' => 'user',
908                         'filearea' => 'draft',
909                         'filepath' => '/',
910                         'filename' => 'not_a_virus.exe'
911                     ],
912                     'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu'
913                 ],
914                 false
915             ]
916         ];
917     }
919     public function test_list_participants() {
920         global $CFG;
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');
930         }
932         $this->setUser($teacher);
933         $assign = $this->create_instance($course, ['grade' => 100]);
935         $this->assertCount(10, $assign->list_participants(null, true));
936     }
938     public function test_list_participants_activeenrol() {
939         global $CFG, $DB;
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');
949         }
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);
954         }
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));
961     }
963     public function test_list_participants_with_group_restriction() {
964         global $CFG;
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, [
979                 'grade' => 100,
980                 'availability' => json_encode(
981                     \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)])
982                 ),
983             ]);
985         groups_add_member($specialgroup, $student);
986         groups_add_member($specialgroup, $otherstudent);
987         $this->assertEquals(2, count($assign->list_participants(null, true)));
988     }
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'));
996     }
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));
1005     }
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);
1019     }
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);
1036     }
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);
1055     }
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);
1080     }
1082     /**
1083      * No active group and non-group submissions disallowed => 2 groups.
1084      */
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());
1103     }
1105     /**
1106      * No active group and non group submissions allowed => 2 groups + the default one.
1107      */
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,
1131             ]);
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));
1139     }
1141     /**
1142      * Active group => just selected one.
1143      */
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,
1166             ]);
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));
1174     }
1176     /**
1177      * Active group => just selected one.
1178      */
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,
1189             ]);
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,
1208             ]);
1209         $this->assertEquals(2, $assign->count_teams());
1210     }
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,
1229             ]);
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));
1246     }
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,
1257             ]);
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));
1270     }
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,
1281             ]);
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));
1294     }
1296     public function test_count_submissions_submitted() {
1297         global $SESSION;
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,
1307             ]);
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));
1320     }
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,
1331             ]);
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));
1346     }
1348     public function test_count_submissions_graded_group() {
1349         global $SESSION;
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,
1363             ]);
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));
1380     }
1382     // TODO
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,
1396             ]);
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));
1461     }
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());
1475     }
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());
1490     }
1492     public function test_cron() {
1493         global $PAGE;
1494         $this->resetAfterTest();
1496         // First run cron so there are no messages waiting to be sent (from other tests).
1497         cron_setup_user();
1498         assign::cron();
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,
1508             ]);
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/');
1515         cron_setup_user();
1516         $sink = $this->redirectMessages();
1517         assign::cron();
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);
1523         // Test customdata.
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         $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
1530         $this->assertEquals(0, $customdata->uniqueidforuser);   // Not used in this case.
1531         $this->assertFalse($customdata->blindmarking);
1532     }
1534     public function test_cron_without_notifications() {
1535         $this->resetAfterTest();
1537         // First run cron so there are no messages waiting to be sent (from other tests).
1538         cron_setup_user();
1539         assign::cron();
1541         $course = $this->getDataGenerator()->create_course();
1542         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1543         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1545         // Now create an assignment and add some feedback.
1546         $this->setUser($teacher);
1547         $assign = $this->create_instance($course, [
1548                 'sendstudentnotifications' => 1,
1549             ]);
1551         $this->add_submission($student, $assign);
1552         $this->submit_for_grading($student, $assign);
1553         $this->mark_submission($teacher, $assign, $student, 50.0, [
1554                 'sendstudentnotifications' => 0,
1555             ]);
1557         cron_setup_user();
1558         $sink = $this->redirectMessages();
1559         assign::cron();
1560         $messages = $sink->get_messages();
1562         $this->assertEquals(0, count($messages));
1563     }
1565     public function test_cron_regraded() {
1566         $this->resetAfterTest();
1568         // First run cron so there are no messages waiting to be sent (from other tests).
1569         cron_setup_user();
1570         assign::cron();
1572         $course = $this->getDataGenerator()->create_course();
1573         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1574         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1576         // Now create an assignment and add some feedback.
1577         $this->setUser($teacher);
1578         $assign = $this->create_instance($course, [
1579                 'sendstudentnotifications' => 1,
1580             ]);
1582         $this->add_submission($student, $assign);
1583         $this->submit_for_grading($student, $assign);
1584         $this->mark_submission($teacher, $assign, $student, 50.0);
1586         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1587         cron_setup_user();
1588         assign::cron();
1590         // Regrade.
1591         $this->mark_submission($teacher, $assign, $student, 50.0);
1593         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1594         cron_setup_user();
1595         $sink = $this->redirectMessages();
1596         assign::cron();
1597         $messages = $sink->get_messages();
1599         $this->assertEquals(1, count($messages));
1600         $this->assertEquals(1, $messages[0]->notification);
1601         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1602     }
1604     /**
1605      * Test delivery of grade notifications as controlled by marking workflow.
1606      */
1607     public function test_markingworkflow_cron() {
1608         $this->resetAfterTest();
1610         // First run cron so there are no messages waiting to be sent (from other tests).
1611         cron_setup_user();
1612         assign::cron();
1614         $course = $this->getDataGenerator()->create_course();
1615         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1616         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1618         // Now create an assignment and add some feedback.
1619         $this->setUser($teacher);
1620         $assign = $this->create_instance($course, [
1621                 'sendstudentnotifications' => 1,
1622                 'markingworkflow' => 1,
1623             ]);
1625         // Mark a submission but set the workflowstate to an unreleased state.
1626         // This should not trigger a notification.
1627         $this->add_submission($student, $assign);
1628         $this->submit_for_grading($student, $assign);
1629         $this->mark_submission($teacher, $assign, $student, 50.0, [
1630                 'sendstudentnotifications' => 1,
1631                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1632             ]);
1634         cron_setup_user();
1635         $sink = $this->redirectMessages();
1636         assign::cron();
1637         $messages = $sink->get_messages();
1639         $this->assertEquals(0, count($messages));
1641         // Transition to the released state.
1642         $this->setUser($teacher);
1643         $submission = $assign->get_user_submission($student->id, true);
1644         $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1645         $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1647         // Now run cron and see that one message was sent.
1648         cron_setup_user();
1649         $sink = $this->redirectMessages();
1650         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1651         assign::cron();
1652         $messages = $sink->get_messages();
1654         $this->assertEquals(1, count($messages));
1655         $this->assertEquals(1, $messages[0]->notification);
1656         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1657     }
1659     public function test_cron_message_includes_courseid() {
1660         $this->resetAfterTest();
1662         // First run cron so there are no messages waiting to be sent (from other tests).
1663         cron_setup_user();
1664         assign::cron();
1666         $course = $this->getDataGenerator()->create_course();
1667         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1668         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1670         // Now create an assignment and add some feedback.
1671         $this->setUser($teacher);
1672         $assign = $this->create_instance($course, [
1673                 'sendstudentnotifications' => 1,
1674             ]);
1676         // Mark a submission but set the workflowstate to an unreleased state.
1677         // This should not trigger a notification.
1678         $this->add_submission($student, $assign);
1679         $this->submit_for_grading($student, $assign);
1680         $this->mark_submission($teacher, $assign, $student);
1681         phpunit_util::stop_message_redirection();
1683         // Now run cron and see that one message was sent.
1684         cron_setup_user();
1685         $this->preventResetByRollback();
1686         $sink = $this->redirectEvents();
1687         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1688         assign::cron();
1690         $events = $sink->get_events();
1691         $event = reset($events);
1692         $this->assertInstanceOf('\core\event\notification_sent', $event);
1693         $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1694         $sink->close();
1695     }
1697     public function test_is_graded() {
1698         $this->resetAfterTest();
1700         $course = $this->getDataGenerator()->create_course();
1701         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1702         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1703         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1705         $assign = $this->create_instance($course);
1707         $this->add_submission($student, $assign);
1708         $this->submit_for_grading($student, $assign);
1709         $this->mark_submission($teacher, $assign, $student, 50.0);
1711         $this->setUser($teacher);
1712         $this->assertEquals(true, $assign->testable_is_graded($student->id));
1713         $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1714     }
1716     public function test_can_grade() {
1717         global $DB;
1719         $this->resetAfterTest();
1721         $course = $this->getDataGenerator()->create_course();
1722         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1723         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1725         $assign = $this->create_instance($course);
1727         $this->setUser($student);
1728         $this->assertEquals(false, $assign->can_grade());
1730         $this->setUser($teacher);
1731         $this->assertEquals(true, $assign->can_grade());
1733         // Test the viewgrades capability for other users.
1734         $this->setUser();
1735         $this->assertTrue($assign->can_grade($teacher->id));
1736         $this->assertFalse($assign->can_grade($student->id));
1738         // Test the viewgrades capability - without mod/assign:grade.
1739         $this->setUser($student);
1741         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1742         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1743         $this->assertEquals(false, $assign->can_grade());
1744     }
1746     public function test_can_view_submission() {
1747         global $DB;
1749         $this->resetAfterTest();
1751         $course = $this->getDataGenerator()->create_course();
1752         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1753         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1754         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1755         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1756         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1758         $assign = $this->create_instance($course);
1760         $this->setUser($student);
1761         $this->assertEquals(true, $assign->can_view_submission($student->id));
1762         $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1763         $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1765         $this->setUser($teacher);
1766         $this->assertEquals(true, $assign->can_view_submission($student->id));
1767         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1768         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1769         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1771         $this->setUser($editingteacher);
1772         $this->assertEquals(true, $assign->can_view_submission($student->id));
1773         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1774         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1775         $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1777         // Test the viewgrades capability - without mod/assign:grade.
1778         $this->setUser($student);
1779         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1780         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1781         $this->assertEquals(true, $assign->can_view_submission($student->id));
1782         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1783         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1784         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1785     }
1787     public function test_update_submission() {
1788         $this->resetAfterTest();
1790         $course = $this->getDataGenerator()->create_course();
1791         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1792         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1794         $assign = $this->create_instance($course);
1796         $this->add_submission($student, $assign);
1797         $submission = $assign->get_user_submission($student->id, 0);
1798         $assign->testable_update_submission($submission, $student->id, true, true);
1800         $this->setUser($teacher);
1802         // Verify the gradebook update.
1803         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1805         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1806         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1807     }
1809     public function test_update_submission_team() {
1810         $this->resetAfterTest();
1812         $course = $this->getDataGenerator()->create_course();
1813         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1814         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1816         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1817         groups_add_member($group, $student);
1819         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1820         groups_add_member($group, $otherstudent);
1822         $assign = $this->create_instance($course, [
1823                 'teamsubmission' => 1,
1824             ]);
1826         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1827         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1828         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1830         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1831         $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1832         $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1834         $this->add_submission($student, $assign);
1835         $submission = $assign->get_group_submission($student->id, 0, true);
1836         $assign->testable_update_submission($submission, $student->id, true, true);
1838         // Verify the gradebook update for the student.
1839         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1841         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1842         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1844         // Verify the gradebook update for the other student.
1845         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1847         $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1848         $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1849     }
1851     public function test_update_submission_suspended() {
1852         $this->resetAfterTest();
1854         $course = $this->getDataGenerator()->create_course();
1855         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1856         $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1858         $assign = $this->create_instance($course);
1860         $this->add_submission($student, $assign);
1861         $submission = $assign->get_user_submission($student->id, 0);
1862         $assign->testable_update_submission($submission, $student->id, true, false);
1864         $this->setUser($teacher);
1866         // Verify the gradebook update.
1867         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1869         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1870         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1871     }
1873     public function test_update_submission_blind() {
1874         $this->resetAfterTest();
1876         $course = $this->getDataGenerator()->create_course();
1877         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1878         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1880         $assign = $this->create_instance($course, [
1881                 'blindmarking' => 1,
1882             ]);
1884         $this->add_submission($student, $assign);
1885         $submission = $assign->get_user_submission($student->id, 0);
1886         $assign->testable_update_submission($submission, $student->id, true, false);
1888         // Verify the gradebook update.
1889         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1891         // The usermodified is not set because this is blind marked.
1892         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1893         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1894     }
1896     public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
1897         global $PAGE;
1899         $this->resetAfterTest();
1901         $course = $this->getDataGenerator()->create_course();
1902         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1903         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1905         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1906         groups_add_member($group, $student);
1908         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1909         groups_add_member($group, $otherstudent);
1911         $assign = $this->create_instance($course, [
1912                 'teamsubmission' => 1,
1913                 'assignsubmission_onlinetext_enabled' => 1,
1914                 'submissiondrafts' => 1,
1915                 'requireallteammemberssubmit' => 1,
1916             ]);
1918         // Now verify group assignments.
1919         $this->setUser($teacher);
1920         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1922         // Add a submission.
1923         $this->add_submission($student, $assign);
1925         // Check we can see the submit button.
1926         $this->setUser($student);
1927         $output = $assign->view_student_summary($student, true);
1928         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1930         $submission = $assign->get_group_submission($student->id, 0, true);
1931         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1932         $assign->testable_update_submission($submission, $student->id, true, true);
1934         // Check that the student does not see "Submit" button.
1935         $output = $assign->view_student_summary($student, true);
1936         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1938         // Change to another user in the same group.
1939         $this->setUser($otherstudent);
1940         $output = $assign->view_student_summary($otherstudent, true);
1941         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1943         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
1944         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1945         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
1946         $output = $assign->view_student_summary($otherstudent, true);
1947         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1948     }
1950     public function test_group_submissions_submit_for_marking() {
1951         global $PAGE;
1953         $this->resetAfterTest();
1955         $course = $this->getDataGenerator()->create_course();
1956         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1957         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1959         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1960         groups_add_member($group, $student);
1962         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1963         groups_add_member($group, $otherstudent);
1965         // Now verify group assignments.
1966         $this->setUser($teacher);
1967         $time = time();
1968         $assign = $this->create_instance($course, [
1969                 'teamsubmission' => 1,
1970                 'assignsubmission_onlinetext_enabled' => 1,
1971                 'submissiondrafts' => 1,
1972                 'requireallteammemberssubmit' => 0,
1973                 'duedate' => $time - (2 * DAYSECS),
1974             ]);
1975         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1977         // Add a submission.
1978         $this->add_submission($student, $assign);
1981         // Check we can see the submit button.
1982         $output = $assign->view_student_summary($student, true);
1983         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1984         $this->assertContains(get_string('timeremaining', 'assign'), $output);
1985         $difftime = time() - $time;
1986         $this->assertContains(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
1988         $submission = $assign->get_group_submission($student->id, 0, true);
1989         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1990         $assign->testable_update_submission($submission, $student->id, true, true);
1992         // Check that the student does not see "Submit" button.
1993         $output = $assign->view_student_summary($student, true);
1994         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1996         // Change to another user in the same group.
1997         $this->setUser($otherstudent);
1998         $output = $assign->view_student_summary($otherstudent, true);
1999         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
2001         // Check that time remaining is not overdue.
2002         $this->assertContains(get_string('timeremaining', 'assign'), $output);
2003         $difftime = time() - $time;
2004         $this->assertContains(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2006         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2007         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2008         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2009         $output = $assign->view_student_summary($otherstudent, true);
2010         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
2011     }
2013     public function test_submissions_open() {
2014         global $DB;
2016         $this->resetAfterTest();
2018         $course = $this->getDataGenerator()->create_course();
2019         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2020         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2021         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2022         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2023         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
2025         $this->setAdminUser();
2027         $now = time();
2028         $tomorrow = $now + DAYSECS;
2029         $oneweek = $now + WEEKSECS;
2030         $yesterday = $now - DAYSECS;
2032         $assign = $this->create_instance($course);
2033         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2035         $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
2036         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2038         $assign = $this->create_instance($course, ['duedate' => $yesterday]);
2039         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2041         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
2042         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2044         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2045         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2047         $assign->testable_save_user_extension($student->id, $tomorrow);
2048         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2050         $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2051         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2053         $this->setUser($student);
2054         $submission = $assign->get_user_submission($student->id, true);
2055         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2056         $assign->testable_update_submission($submission, $student->id, true, false);
2058         $this->setUser($teacher);
2059         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2060     }
2062     public function test_get_graders() {
2063         global $DB;
2065         $this->resetAfterTest();
2067         $course = $this->getDataGenerator()->create_course();
2068         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2069         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2070         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2072         $this->setAdminUser();
2074         // Create an assignment with no groups.
2075         $assign = $this->create_instance($course);
2076         $this->assertCount(2, $assign->testable_get_graders($student->id));
2077     }
2079     public function test_get_graders_separate_groups() {
2080         global $DB;
2082         $this->resetAfterTest();
2084         $course = $this->getDataGenerator()->create_course();
2085         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2086         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2087         $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2088         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2089         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2090         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2092         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2093         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2094         groups_add_member($group1, $student);
2096         $this->setAdminUser();
2098         // Force create an assignment with SEPARATEGROUPS.
2099         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2100         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2102         $assign = $this->create_instance($course, [
2103                 'groupingid' => $grouping->id,
2104                 'groupmode' => SEPARATEGROUPS,
2105             ]);
2107         $this->assertCount(4, $assign->testable_get_graders($student->id));
2109         // Note the second student is in a group that is not in the grouping.
2110         // This means that we get all graders that are not in a group in the grouping.
2111         $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2112     }
2114     public function test_get_notified_users() {
2115         global $CFG, $DB;
2117         $this->resetAfterTest();
2119         $course = $this->getDataGenerator()->create_course();
2120         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2121         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2122         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2124         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2125         groups_add_member($group1, $teacher);
2127         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2128         groups_add_member($group1, $editingteacher);
2130         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2131         groups_add_member($group1, $student);
2133         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2134         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2136         $capability = 'mod/assign:receivegradernotifications';
2137         $coursecontext = context_course::instance($course->id);
2138         $role = $DB->get_record('role', array('shortname' => 'teacher'));
2140         $this->setUser($teacher);
2142         // Create an assignment with no groups.
2143         $assign = $this->create_instance($course);
2145         $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2147         // Change nonediting teachers role to not receive grader notifications.
2148         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2150         // Only the editing teachers will be returned.
2151         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2153         // Note the second student is in a group that is not in the grouping.
2154         // This means that we get all graders that are not in a group in the grouping.
2155         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2156     }
2158     public function test_get_notified_users_in_grouping() {
2159         global $CFG, $DB;
2161         $this->resetAfterTest();
2163         $course = $this->getDataGenerator()->create_course();
2164         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2165         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2166         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2168         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2169         groups_add_member($group1, $teacher);
2171         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2172         groups_add_member($group1, $editingteacher);
2174         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2175         groups_add_member($group1, $student);
2177         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2178         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2180         // Force create an assignment with SEPARATEGROUPS.
2181         $assign = $this->create_instance($course, [
2182                 'groupingid' => $grouping->id,
2183                 'groupmode' => SEPARATEGROUPS,
2184             ]);
2186         // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2187         $this->setUser($student);
2188         $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2190         // Note the second student is in a group that is not in the grouping.
2191         // This means that we get all graders that are not in a group in the grouping.
2192         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2194         // Change nonediting teachers role to not receive grader notifications.
2195         $capability = 'mod/assign:receivegradernotifications';
2196         $coursecontext = context_course::instance($course->id);
2197         $role = $DB->get_record('role', ['shortname' => 'teacher']);
2198         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2200         // Only the editing teachers will be returned.
2201         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2203         // Note the second student is in a group that is not in the grouping.
2204         // This means that we get all graders that are not in a group in the grouping.
2205         // Unfortunately there are no editing teachers who are not in a group.
2206         $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2207     }
2209     public function test_group_members_only() {
2210         global $CFG;
2212         $this->resetAfterTest();
2214         $course = $this->getDataGenerator()->create_course();
2215         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2216         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2217         $this->getDataGenerator()->create_grouping_group([
2218                 'groupid' => $group1->id,
2219                 'groupingid' => $grouping->id,
2220             ]);
2222         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2223         $this->getDataGenerator()->create_grouping_group([
2224                 'groupid' => $group2->id,
2225                 'groupingid' => $grouping->id,
2226             ]);
2228         $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2230         // Add users in the following groups
2231         // - Teacher - Group 1.
2232         // - Student - Group 1.
2233         // - Student - Group 2.
2234         // - Student - Unrelated Group
2235         // - Student - No group.
2236         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2237         groups_add_member($group1, $teacher);
2239         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2240         groups_add_member($group1, $student);
2242         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2243         groups_add_member($group2, $otherstudent);
2245         $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2246         groups_add_member($group2, $otherstudent);
2248         $this->getDataGenerator()->create_and_enrol($course, 'student');
2250         $this->setAdminUser();
2252         $CFG->enableavailability = true;
2253         $assign = $this->create_instance($course, [], [
2254                 'availability' => json_encode(
2255                     \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2256                 ),
2257                 'groupingid' => $grouping->id,
2258             ]);
2260         // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2261         // group, or the student in an unrelated group.
2262         $this->setUser($teacher);
2263         $participants = $assign->list_participants(0, true);
2264         $this->assertCount(2, $participants);
2265         $this->assertTrue(isset($participants[$student->id]));
2266         $this->assertTrue(isset($participants[$otherstudent->id]));
2267     }
2269     public function test_get_uniqueid_for_user() {
2270         $this->resetAfterTest();
2272         $course = $this->getDataGenerator()->create_course();
2273         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2274         $students = [];
2275         for ($i = 0; $i < 10; $i++) {
2276             $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2277             $students[$student->id] = $student;
2278         }
2280         $this->setUser($teacher);
2281         $assign = $this->create_instance($course);
2283         foreach ($students as $student) {
2284             $uniqueid = $assign->get_uniqueid_for_user($student->id);
2285             $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2286         }
2287     }
2289     public function test_show_student_summary() {
2290         global $CFG, $PAGE;
2292         $this->resetAfterTest();
2294         $course = $this->getDataGenerator()->create_course();
2295         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2296         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2297         $this->setUser($teacher);
2298         $assign = $this->create_instance($course);
2299         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2301         // No feedback should be available because this student has not been graded.
2302         $this->setUser($student);
2303         $output = $assign->view_student_summary($student, true);
2304         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if there is no grade');
2306         // Simulate adding a grade.
2307         $this->add_submission($student, $assign);
2308         $this->submit_for_grading($student, $assign);
2309         $this->mark_submission($teacher, $assign, $student);
2311         // Now we should see the feedback.
2312         $this->setUser($student);
2313         $output = $assign->view_student_summary($student, true);
2314         $this->assertRegexp('/Feedback/', $output, 'Show feedback if there is a grade');
2316         // Now hide the grade in gradebook.
2317         $this->setUser($teacher);
2318         require_once($CFG->libdir.'/gradelib.php');
2319         $gradeitem = new grade_item(array(
2320             'itemtype'      => 'mod',
2321             'itemmodule'    => 'assign',
2322             'iteminstance'  => $assign->get_instance()->id,
2323             'courseid'      => $course->id));
2325         $gradeitem->set_hidden(1, false);
2327         // No feedback should be available because the grade is hidden.
2328         $this->setUser($student);
2329         $output = $assign->view_student_summary($student, true);
2330         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2331     }
2333     public function test_show_student_summary_with_feedback() {
2334         global $CFG, $PAGE;
2336         $this->resetAfterTest();
2338         $course = $this->getDataGenerator()->create_course();
2339         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2340         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2341         $this->setUser($teacher);
2342         $assign = $this->create_instance($course, [
2343                 'assignfeedback_comments_enabled' => 1
2344             ]);
2345         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2347         // No feedback should be available because this student has not been graded.
2348         $this->setUser($student);
2349         $output = $assign->view_student_summary($student, true);
2350         $this->assertNotRegexp('/Feedback/', $output);
2352         // Simulate adding a grade.
2353         $this->add_submission($student, $assign);
2354         $this->submit_for_grading($student, $assign);
2355         $this->mark_submission($teacher, $assign, $student, null, [
2356                 'assignfeedbackcomments_editor' => [
2357                     'text' => 'Tomato sauce',
2358                     'format' => FORMAT_MOODLE,
2359                 ],
2360             ]);
2362         // Should have feedback but no grade.
2363         $this->setUser($student);
2364         $output = $assign->view_student_summary($student, true);
2365         $this->assertRegexp('/Feedback/', $output);
2366         $this->assertRegexp('/Tomato sauce/', $output);
2367         $this->assertNotRegexp('/Grade/', $output, 'Do not show grade when there is no grade.');
2368         $this->assertNotRegexp('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2370         // Add a grade now.
2371         $this->mark_submission($teacher, $assign, $student, 50.0, [
2372                 'assignfeedbackcomments_editor' => [
2373                     'text' => 'Bechamel sauce',
2374                     'format' => FORMAT_MOODLE,
2375                 ],
2376             ]);
2378         // Should have feedback but no grade.
2379         $this->setUser($student);
2380         $output = $assign->view_student_summary($student, true);
2381         $this->assertNotRegexp('/Tomato sauce/', $output);
2382         $this->assertRegexp('/Bechamel sauce/', $output);
2383         $this->assertRegexp('/Grade/', $output);
2384         $this->assertRegexp('/Graded on/', $output);
2386         // Now hide the grade in gradebook.
2387         $this->setUser($teacher);
2388         $gradeitem = new grade_item(array(
2389             'itemtype'      => 'mod',
2390             'itemmodule'    => 'assign',
2391             'iteminstance'  => $assign->get_instance()->id,
2392             'courseid'      => $course->id));
2394         $gradeitem->set_hidden(1, false);
2396         // No feedback should be available because the grade is hidden.
2397         $this->setUser($student);
2398         $output = $assign->view_student_summary($student, true);
2399         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2400     }
2402     /**
2403      * Test reopen behavior when in "Manual" mode.
2404      */
2405     public function test_attempt_reopen_method_manual() {
2406         global $PAGE;
2408         $this->resetAfterTest();
2409         $course = $this->getDataGenerator()->create_course();
2410         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2411         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2413         $assign = $this->create_instance($course, [
2414                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2415                 'maxattempts' => 3,
2416                 'submissiondrafts' => 1,
2417                 'assignsubmission_onlinetext_enabled' => 1,
2418             ]);
2419         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2421         // Student should be able to see an add submission button.
2422         $this->setUser($student);
2423         $output = $assign->view_student_summary($student, true);
2424         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2426         // Add a submission.
2427         $this->add_submission($student, $assign);
2428         $this->submit_for_grading($student, $assign);
2430         // Verify the student cannot make changes to the submission.
2431         $output = $assign->view_student_summary($student, true);
2432         $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2434         // Mark the submission.
2435         $this->mark_submission($teacher, $assign, $student);
2437         // Check the student can see the grade.
2438         $this->setUser($student);
2439         $output = $assign->view_student_summary($student, true);
2440         $this->assertNotEquals(false, strpos($output, '50.0'));
2442         // Allow the student another attempt.
2443         $teacher->ignoresesskey = true;
2444         $this->setUser($teacher);
2445         $result = $assign->testable_process_add_attempt($student->id);
2446         $this->assertEquals(true, $result);
2448         // Check that the previous attempt is now in the submission history table.
2449         $this->setUser($student);
2450         $output = $assign->view_student_summary($student, true);
2451         // Need a better check.
2452         $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2454         // Check that the student now has a button for Add a new attempt".
2455         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2456         // Check that the student now does not have a button for Submit.
2457         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2459         // Check that the student now has a submission history.
2460         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2462         $this->setUser($teacher);
2463         // Check that the grading table loads correctly and contains this user.
2464         // This is also testing that we do not get duplicate rows in the grading table.
2465         $gradingtable = new assign_grading_table($assign, 100, '', 0, true);
2466         $output = $assign->get_renderer()->render($gradingtable);
2467         $this->assertEquals(true, strpos($output, $student->lastname));
2469         // Should be 1 not 2.
2470         $this->assertEquals(1, $assign->count_submissions());
2471         $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2472         $this->assertEquals(0, $assign->count_submissions_need_grading());
2473         $this->assertEquals(1, $assign->count_grades());
2475         // Change max attempts to unlimited.
2476         $formdata = clone($assign->get_instance());
2477         $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2478         $formdata->instance = $formdata->id;
2479         $assign->update_instance($formdata);
2481         // Mark the submission again.
2482         $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2484         // Check the grade exists.
2485         $this->setUser($teacher);
2486         $grades = $assign->get_user_grades_for_gradebook($student->id);
2487         $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2489         // Check we can reopen still.
2490         $result = $assign->testable_process_add_attempt($student->id);
2491         $this->assertEquals(true, $result);
2493         // Should no longer have a grade because there is no grade for the latest attempt.
2494         $grades = $assign->get_user_grades_for_gradebook($student->id);
2495         $this->assertEmpty($grades);
2496     }
2498     /**
2499      * Test reopen behavior when in "Reopen until pass" mode.
2500      */
2501     public function test_attempt_reopen_method_untilpass() {
2502         global $PAGE;
2504         $this->resetAfterTest();
2505         $course = $this->getDataGenerator()->create_course();
2506         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2507         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2509         $assign = $this->create_instance($course, [
2510                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2511                 'maxattempts' => 3,
2512                 'submissiondrafts' => 1,
2513                 'assignsubmission_onlinetext_enabled' => 1,
2514             ]);
2515         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2517         // Set grade to pass to 80.
2518         $gradeitem = $assign->get_grade_item();
2519         $gradeitem->gradepass = '80.0';
2520         $gradeitem->update();
2522         // Student should be able to see an add submission button.
2523         $this->setUser($student);
2524         $output = $assign->view_student_summary($student, true);
2525         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2527         // Add a submission.
2528         $this->add_submission($student, $assign);
2529         $this->submit_for_grading($student, $assign);
2531         // Verify the student cannot make a new attempt.
2532         $output = $assign->view_student_summary($student, true);
2533         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2535         // Mark the submission as non-passing.
2536         $this->mark_submission($teacher, $assign, $student, 50.0);
2538         // Check the student can see the grade.
2539         $this->setUser($student);
2540         $output = $assign->view_student_summary($student, true);
2541         $this->assertNotEquals(false, strpos($output, '50.0'));
2543         // Check that the student now has a button for Add a new attempt.
2544         $output = $assign->view_student_summary($student, true);
2545         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2547         // Check that the student now does not have a button for Submit.
2548         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2550         // Check that the student now has a submission history.
2551         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2553         // Add a second submission.
2554         $this->add_submission($student, $assign);
2555         $this->submit_for_grading($student, $assign);
2557         // Mark the submission as passing.
2558         $this->mark_submission($teacher, $assign, $student, 80.0);
2560         // Check that the student does not have a button for Add a new attempt.
2561         $this->setUser($student);
2562         $output = $assign->view_student_summary($student, true);
2563         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2565         // Re-mark the submission as not passing.
2566         $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2568         // Check that the student now has a button for Add a new attempt.
2569         $this->setUser($student);
2570         $output = $assign->view_student_summary($student, true);
2571         $this->assertRegexp('/' . get_string('addnewattempt', 'assign') . '/', $output);
2572         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2573     }
2575     public function test_attempt_reopen_method_untilpass_passing() {
2576         global $PAGE;
2578         $this->resetAfterTest();
2579         $course = $this->getDataGenerator()->create_course();
2580         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2581         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2583         $assign = $this->create_instance($course, [
2584                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2585                 'maxattempts' => 3,
2586                 'submissiondrafts' => 1,
2587                 'assignsubmission_onlinetext_enabled' => 1,
2588             ]);
2589         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2591         // Set grade to pass to 80.
2592         $gradeitem = $assign->get_grade_item();
2593         $gradeitem->gradepass = '80.0';
2594         $gradeitem->update();
2596         // Student should be able to see an add submission button.
2597         $this->setUser($student);
2598         $output = $assign->view_student_summary($student, true);
2599         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2601         // Add a submission as a student.
2602         $this->add_submission($student, $assign);
2603         $this->submit_for_grading($student, $assign);
2605         // Mark the submission as passing.
2606         $this->mark_submission($teacher, $assign, $student, 100.0);
2608         // Check the student can see the grade.
2609         $this->setUser($student);
2610         $output = $assign->view_student_summary($student, true);
2611         $this->assertNotEquals(false, strpos($output, '100.0'));
2613         // Check that the student does not have a button for Add a new attempt.
2614         $output = $assign->view_student_summary($student, true);
2615         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2616     }
2618     public function test_attempt_reopen_method_untilpass_no_passing_requirement() {
2619         global $PAGE;
2621         $this->resetAfterTest();
2622         $course = $this->getDataGenerator()->create_course();
2623         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2624         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2626         $assign = $this->create_instance($course, [
2627                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2628                 'maxattempts' => 3,
2629                 'submissiondrafts' => 1,
2630                 'assignsubmission_onlinetext_enabled' => 1,
2631             ]);
2632         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2634         // Set grade to pass to 0, so that no attempts should reopen.
2635         $gradeitem = $assign->get_grade_item();
2636         $gradeitem->gradepass = '0';
2637         $gradeitem->update();
2639         // Student should be able to see an add submission button.
2640         $this->setUser($student);
2641         $output = $assign->view_student_summary($student, true);
2642         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2644         // Add a submission.
2645         $this->add_submission($student, $assign);
2646         $this->submit_for_grading($student, $assign);
2648         // Mark the submission with any grade.
2649         $this->mark_submission($teacher, $assign, $student, 0.0);
2651         // Check the student can see the grade.
2652         $this->setUser($student);
2653         $output = $assign->view_student_summary($student, true);
2654         $this->assertNotEquals(false, strpos($output, '0.0'));
2656         // Check that the student does not have a button for Add a new attempt.
2657         $output = $assign->view_student_summary($student, true);
2658         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2659     }
2661     /**
2662      * Test student visibility for each stage of the marking workflow.
2663      */
2664     public function test_markingworkflow() {
2665         global $PAGE;
2667         $this->resetAfterTest();
2668         $course = $this->getDataGenerator()->create_course();
2669         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2670         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2672         $assign = $this->create_instance($course, [
2673                 'markingworkflow' => 1,
2674             ]);
2676         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2678         // Mark the submission and set to notmarked.
2679         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2680                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2681             ]);
2683         // Check the student can't see the grade.
2684         $this->setUser($student);
2685         $output = $assign->view_student_summary($student, true);
2686         $this->assertEquals(false, strpos($output, '50.0'));
2688         // Make sure the grade isn't pushed to the gradebook.
2689         $grades = $assign->get_user_grades_for_gradebook($student->id);
2690         $this->assertEmpty($grades);
2692         // Mark the submission and set to inmarking.
2693         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2694                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2695             ]);
2697         // Check the student can't see the grade.
2698         $this->setUser($student);
2699         $output = $assign->view_student_summary($student, true);
2700         $this->assertEquals(false, strpos($output, '50.0'));
2702         // Make sure the grade isn't pushed to the gradebook.
2703         $grades = $assign->get_user_grades_for_gradebook($student->id);
2704         $this->assertEmpty($grades);
2706         // Mark the submission and set to readyforreview.
2707         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2708                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2709             ]);
2711         // Check the student can't see the grade.
2712         $this->setUser($student);
2713         $output = $assign->view_student_summary($student, true);
2714         $this->assertEquals(false, strpos($output, '50.0'));
2716         // Make sure the grade isn't pushed to the gradebook.
2717         $grades = $assign->get_user_grades_for_gradebook($student->id);
2718         $this->assertEmpty($grades);
2720         // Mark the submission and set to inreview.
2721         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2722                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2723             ]);
2725         // Check the student can't see the grade.
2726         $this->setUser($student);
2727         $output = $assign->view_student_summary($student, true);
2728         $this->assertEquals(false, strpos($output, '50.0'));
2730         // Make sure the grade isn't pushed to the gradebook.
2731         $grades = $assign->get_user_grades_for_gradebook($student->id);
2732         $this->assertEmpty($grades);
2734         // Mark the submission and set to readyforrelease.
2735         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2736                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
2737             ]);
2739         // Check the student can't see the grade.
2740         $this->setUser($student);
2741         $output = $assign->view_student_summary($student, true);
2742         $this->assertEquals(false, strpos($output, '50.0'));
2744         // Make sure the grade isn't pushed to the gradebook.
2745         $grades = $assign->get_user_grades_for_gradebook($student->id);
2746         $this->assertEmpty($grades);
2748         // Mark the submission and set to released.
2749         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2750                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
2751             ]);
2753         // Check the student can see the grade.
2754         $this->setUser($student);
2755         $output = $assign->view_student_summary($student, true);
2756         $this->assertNotEquals(false, strpos($output, '50.0'));
2758         // Make sure the grade is pushed to the gradebook.
2759         $grades = $assign->get_user_grades_for_gradebook($student->id);
2760         $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
2761     }
2763     /**
2764      * Test that a student allocated a specific marker is only shown to that marker.
2765      */
2766     public function test_markerallocation() {
2767         global $PAGE;
2769         $this->resetAfterTest();
2770         $course = $this->getDataGenerator()->create_course();
2771         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2772         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2773         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2775         $assign = $this->create_instance($course, [
2776                 'markingworkflow' => 1,
2777                 'markingallocation' => 1
2778             ]);
2780         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2782         // Allocate marker to submission.
2783         $this->mark_submission($teacher, $assign, $student, null, [
2784             'allocatedmarker' => $teacher->id,
2785         ]);
2787         // Check the allocated marker can view the submission.
2788         $this->setUser($teacher);
2789         $users = $assign->list_participants(0, true);
2790         $this->assertEquals(1, count($users));
2791         $this->assertTrue(isset($users[$student->id]));
2793         $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2794         $context = context_module::instance($cm->id);
2795         $assign = new mod_assign_testable_assign($context, $cm, $course);
2797         // Check that other teachers can't view this submission.
2798         $this->setUser($otherteacher);
2799         $users = $assign->list_participants(0, true);
2800         $this->assertEquals(0, count($users));
2801     }
2803     /**
2804      * Ensure that a teacher cannot submit for students as standard.
2805      */
2806     public function test_teacher_submit_for_student() {
2807         global $PAGE;
2809         $this->resetAfterTest();
2810         $course = $this->getDataGenerator()->create_course();
2811         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2812         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2814         $assign = $this->create_instance($course, [
2815             'assignsubmission_onlinetext_enabled' => 1,
2816             'submissiondrafts' => 1,
2817         ]);
2819         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2821         // Add a submission but do not submit.
2822         $this->add_submission($student, $assign, 'Student submission text');
2824         $this->setUser($student);
2825         $output = $assign->view_student_summary($student, true);
2826         $this->assertContains('Student submission text', $output, 'Contains student submission text');
2828         // Check that a teacher can not edit the submission as they do not have the capability.
2829         $this->setUser($teacher);
2830         $this->expectException('moodle_exception');
2831         $this->expectExceptionMessage('error/nopermission');
2832         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2833     }
2835     /**
2836      * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
2837      */
2838     public function test_teacher_submit_for_student_with_capability() {
2839         global $PAGE;
2841         $this->resetAfterTest();
2842         $course = $this->getDataGenerator()->create_course();
2843         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2844         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2845         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2847         $assign = $this->create_instance($course, [
2848             'assignsubmission_onlinetext_enabled' => 1,
2849             'submissiondrafts' => 1,
2850         ]);
2852         // Add the required capability.
2853         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2854         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2855         role_assign($roleid, $teacher->id, $assign->get_context()->id);
2856         accesslib_clear_all_caches_for_unit_testing();
2858         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2860         // Add a submission but do not submit.
2861         $this->add_submission($student, $assign, 'Student submission text');
2863         $this->setUser($student);
2864         $output = $assign->view_student_summary($student, true);
2865         $this->assertContains('Student submission text', $output, 'Contains student submission text');
2867         // Check that a teacher can edit the submission.
2868         $this->setUser($teacher);
2869         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2871         $this->setUser($student);
2872         $output = $assign->view_student_summary($student, true);
2873         $this->assertNotContains('Student submission text', $output, 'Contains student submission text');
2874         $this->assertContains('Teacher edited submission text', $output, 'Contains teacher edited submission text');
2876         // Check that the teacher can submit the students work.
2877         $this->setUser($teacher);
2878         $this->submit_for_grading($student, $assign, [], false);
2880         // Revert to draft so the student can edit it.
2881         $assign->revert_to_draft($student->id);
2883         $this->setUser($student);
2885         // Check that the submission text was saved.
2886         $output = $assign->view_student_summary($student, true);
2887         $this->assertContains('Teacher edited submission text', $output, 'Contains student submission text');
2889         // Check that the student can submit their work.
2890         $this->submit_for_grading($student, $assign, []);
2892         $output = $assign->view_student_summary($student, true);
2893         $this->assertNotContains(get_string('addsubmission', 'assign'), $output);
2895         // An editing teacher without the extra role should still be able to revert to draft.
2896         $this->setUser($otherteacher);
2898         // Revert to draft so the submission is editable.
2899         $assign->revert_to_draft($student->id);
2900     }
2902     /**
2903      * Ensure that disabling submit after the cutoff date works as expected.
2904      */
2905     public function test_disable_submit_after_cutoff_date() {
2906         global $PAGE;
2908         $this->resetAfterTest();
2909         $course = $this->getDataGenerator()->create_course();
2910         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2912         $now = time();
2913         $tomorrow = $now + DAYSECS;
2914         $lastweek = $now - (7 * DAYSECS);
2915         $yesterday = $now - DAYSECS;
2917         $this->setAdminUser();
2918         $assign = $this->create_instance($course, [
2919                 'duedate' => $yesterday,
2920                 'cutoffdate' => $tomorrow,
2921                 'assignsubmission_onlinetext_enabled' => 1,
2922             ]);
2924         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2926         // Student should be able to see an add submission button.
2927         $this->setUser($student);
2928         $output = $assign->view_student_summary($student, true);
2929         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2931         // Add a submission but don't submit now.
2932         $this->add_submission($student, $assign);
2934         // Create another instance with cut-off and due-date already passed.
2935         $this->setAdminUser();
2936         $assign = $this->create_instance($course, [
2937                 'duedate' => $lastweek,
2938                 'cutoffdate' => $yesterday,
2939                 'assignsubmission_onlinetext_enabled' => 1,
2940             ]);
2942         $this->setUser($student);
2943         $output = $assign->view_student_summary($student, true);
2944         $this->assertNotContains($output, get_string('editsubmission', 'assign'),
2945                                  'Should not be able to edit after cutoff date.');
2946         $this->assertNotContains($output, get_string('submitassignment', 'assign'),
2947                                  'Should not be able to submit after cutoff date.');
2948     }
2950     /**
2951      * Testing for submission comment plugin settings.
2952      *
2953      * @dataProvider submission_plugin_settings_provider
2954      * @param   bool    $globalenabled
2955      * @param   array   $instanceconfig
2956      * @param   bool    $isenabled
2957      */
2958     public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled) {
2959         global $CFG;
2961         $this->resetAfterTest();
2962         $course = $this->getDataGenerator()->create_course();
2964         $CFG->usecomments = $globalenabled;
2965         $assign = $this->create_instance($course, $instanceconfig);
2966         $plugin = $assign->get_submission_plugin_by_type('comments');
2967         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
2968     }
2970     public function submission_plugin_settings_provider() {
2971         return [
2972             'CFG->usecomments true, empty config => Enabled by default' => [
2973                 true,
2974                 [],
2975                 true,
2976             ],
2977             'CFG->usecomments true, config enabled => Comments enabled' => [
2978                 true,
2979                 [
2980                     'assignsubmission_comments_enabled' => 1,
2981                 ],
2982                 true,
2983             ],
2984             'CFG->usecomments true, config idisabled => Comments enabled' => [
2985                 true,
2986                 [
2987                     'assignsubmission_comments_enabled' => 0,
2988                 ],
2989                 true,
2990             ],
2991             'CFG->usecomments false, empty config => Disabled by default' => [
2992                 false,
2993                 [],
2994                 false,
2995             ],
2996             'CFG->usecomments false, config enabled => Comments disabled' => [
2997                 false,
2998                 [
2999                     'assignsubmission_comments_enabled' => 1,
3000                 ],
3001                 false,
3002             ],
3003             'CFG->usecomments false, config disabled => Comments disabled' => [
3004                 false,
3005                 [
3006                     'assignsubmission_comments_enabled' => 0,
3007                 ],
3008                 false,
3009             ],
3010         ];
3011     }
3013     /**
3014      * Testing for comment inline settings
3015      */
3016     public function test_feedback_comment_commentinline() {
3017         global $CFG, $USER;
3019         $this->resetAfterTest();
3020         $course = $this->getDataGenerator()->create_course();
3021         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3022         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3024         $sourcetext = "Hello!
3026 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3028 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3029 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3031 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
3032 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
3033 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
3034 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
3035 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
3036 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3037 ";
3039         $this->setUser($teacher);
3040         $assign = $this->create_instance($course, [
3041                 'assignsubmission_onlinetext_enabled' => 1,
3042                 'assignfeedback_comments_enabled' => 1,
3043                 'assignfeedback_comments_commentinline' => 1,
3044             ]);
3046         $this->setUser($student);
3048         // Add a submission but don't submit now.
3049         $this->add_submission($student, $assign, $sourcetext);
3051         $this->setUser($teacher);
3053         $data = new stdClass();
3054         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3055         $pagination = [
3056                 'userid' => $student->id,
3057                 'rownum' => 0,
3058                 'last' => true,
3059                 'useridlistid' => $assign->get_useridlist_key_id(),
3060                 'attemptnumber' => 0,
3061             ];
3062         $formparams = array($assign, $data, $pagination);
3063         $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
3065         // We need to get the URL these will be transformed to.
3066         $context = context_user::instance($USER->id);
3067         $itemid = $data->assignfeedbackcomments_editor['itemid'];
3068         $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid;
3070         // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
3071         $filteredtext = "Hello!
3073 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3075 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3076 Plugin url outside a tag: $url/logo-240x60.gif
3078 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3079 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3080 Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" />
3081 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" />
3082 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a>
3083 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3084 ";
3086         $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3087     }
3089     /**
3090      * Testing for feedback comment plugin settings.
3091      *
3092      * @dataProvider feedback_plugin_settings_provider
3093      * @param   array   $instanceconfig
3094      * @param   bool    $isenabled
3095      */
3096     public function test_feedback_plugin_settings($instanceconfig, $isenabled) {
3097         $this->resetAfterTest();
3098         $course = $this->getDataGenerator()->create_course();
3100         $assign = $this->create_instance($course, $instanceconfig);
3101         $plugin = $assign->get_feedback_plugin_by_type('comments');
3102         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3103     }
3105     public function feedback_plugin_settings_provider() {
3106         return [
3107             'No configuration => disabled' => [
3108                 [],
3109                 false,
3110             ],
3111             'Actively disabled' => [
3112                 [
3113                     'assignfeedback_comments_enabled' => 0,
3114                 ],
3115                 false,
3116             ],
3117             'Actively enabled' => [
3118                 [
3119                     'assignfeedback_comments_enabled' => 1,
3120                 ],
3121                 true,
3122             ],
3123         ];
3124     }
3126     /**
3127      * Testing if gradebook feedback plugin is enabled.
3128      */
3129     public function test_is_gradebook_feedback_enabled() {
3130         $this->resetAfterTest();
3131         $course = $this->getDataGenerator()->create_course();
3132         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3133         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3135         $adminconfig = get_config('assign');
3136         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3138         // Create assignment with gradebook feedback enabled and grade = 0.
3139         $assign = $this->create_instance($course, [
3140                 "{$gradebookplugin}_enabled" => 1,
3141                 'grades' => 0,
3142             ]);
3144         // Get gradebook feedback plugin.
3145         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3146         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3147         $this->assertEquals(1, $plugin->is_enabled('enabled'));
3148         $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3149     }
3151     /**
3152      * Testing if gradebook feedback plugin is disabled.
3153      */
3154     public function test_is_gradebook_feedback_disabled() {
3155         $this->resetAfterTest();
3156         $course = $this->getDataGenerator()->create_course();
3157         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3158         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3160         $adminconfig = get_config('assign');
3161         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3163         // Create assignment with gradebook feedback disabled and grade = 0.
3164         $assign = $this->create_instance($course, [
3165                 "{$gradebookplugin}_enabled" => 0,
3166                 'grades' => 0,
3167             ]);
3169         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3170         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3171         $this->assertEquals(0, $plugin->is_enabled('enabled'));
3172     }
3174     /**
3175      * Testing can_edit_submission.
3176      */
3177     public function test_can_edit_submission() {
3178         $this->resetAfterTest();
3179         $course = $this->getDataGenerator()->create_course();
3180         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3181         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3182         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3184         $assign = $this->create_instance($course, [
3185                 'assignsubmission_onlinetext_enabled' => 1,
3186                 'submissiondrafts' => 1,
3187             ]);
3189         // Check student can edit their own submission.
3190         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3192         // Check student cannot edit others submission.
3193         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3195         // Check teacher cannot (by default) edit a students submission.
3196         $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3197     }
3199     /**
3200      * Testing can_edit_submission with the editothersubmission capability.
3201      */
3202     public function test_can_edit_submission_with_editothersubmission() {
3203         $this->resetAfterTest();
3204         $course = $this->getDataGenerator()->create_course();
3205         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3206         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3207         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3209         $assign = $this->create_instance($course, [
3210                 'assignsubmission_onlinetext_enabled' => 1,
3211                 'submissiondrafts' => 1,
3212             ]);
3214         // Add the required capability to edit a student submission.
3215         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3216         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3217         role_assign($roleid, $teacher->id, $assign->get_context()->id);
3218         accesslib_clear_all_caches_for_unit_testing();
3220         // Check student can edit their own submission.
3221         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3223         // Check student cannot edit others submission.
3224         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3226         // Retest - should now have access.
3227         $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3228     }
3230     /**
3231      * Testing can_edit_submission
3232      */
3233     public function test_can_edit_submission_separategroups() {
3234         $this->resetAfterTest();
3235         $course = $this->getDataGenerator()->create_course();
3236         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3238         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3239         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3240         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3241         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3243         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3244         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3245         groups_assign_grouping($grouping->id, $group1->id);
3246         groups_add_member($group1, $student1);
3247         groups_add_member($group1, $student2);
3249         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3250         groups_assign_grouping($grouping->id, $group2->id);
3251         groups_add_member($group2, $student3);
3252         groups_add_member($group2, $student4);
3254         $assign = $this->create_instance($course, [
3255                 'assignsubmission_onlinetext_enabled' => 1,
3256                 'submissiondrafts' => 1,
3257                 'groupingid' => $grouping->id,
3258                 'groupmode' => SEPARATEGROUPS,
3259             ]);
3261         // Verify a student does not have the ability to edit submissions for other users.
3262         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3263         $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id));
3264         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3265         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3266     }
3268     /**
3269      * Testing can_edit_submission
3270      */
3271     public function test_can_edit_submission_separategroups_with_editothersubmission() {
3272         $this->resetAfterTest();
3273         $course = $this->getDataGenerator()->create_course();
3274         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3276         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3277         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3278         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3279         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3281         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3282         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3283         groups_assign_grouping($grouping->id, $group1->id);
3284         groups_add_member($group1, $student1);
3285         groups_add_member($group1, $student2);
3287         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3288         groups_assign_grouping($grouping->id, $group2->id);
3289         groups_add_member($group2, $student3);
3290         groups_add_member($group2, $student4);
3292         $assign = $this->create_instance($course, [
3293                 'assignsubmission_onlinetext_enabled' => 1,
3294                 'submissiondrafts' => 1,
3295                 'groupingid' => $grouping->id,
3296                 'groupmode' => SEPARATEGROUPS,
3297             ]);
3299         // Add the capability to the new assignment for student 1.
3300         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3301         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3302         role_assign($roleid, $student1->id, $assign->get_context()->id);
3303         accesslib_clear_all_caches_for_unit_testing();
3305         // Verify student1 has the ability to edit submissions for other users in their group, but not other groups.
3306         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3307         $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id));
3308         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3309         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3311         // Verify other students do not have the ability to edit submissions for other users.
3312         $this->assertTrue($assign->can_edit_submission($student2->id, $student2->id));
<