Merge branch 'MDL-62715-master' of git://github.com/andrewnicols/moodle
[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         $this->resetAfterTest();
1495         // First run cron so there are no messages waiting to be sent (from other tests).
1496         cron_setup_user();
1497         assign::cron();
1499         $course = $this->getDataGenerator()->create_course();
1500         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1501         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1503         // Now create an assignment and add some feedback.
1504         $this->setUser($teacher);
1505         $assign = $this->create_instance($course, [
1506                 'sendstudentnotifications' => 1,
1507             ]);
1509         $this->add_submission($student, $assign);
1510         $this->submit_for_grading($student, $assign);
1511         $this->mark_submission($teacher, $assign, $student, 50.0);
1513         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1514         cron_setup_user();
1515         $sink = $this->redirectMessages();
1516         assign::cron();
1517         $messages = $sink->get_messages();
1519         $this->assertEquals(1, count($messages));
1520         $this->assertEquals(1, $messages[0]->notification);
1521         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1522     }
1524     public function test_cron_without_notifications() {
1525         $this->resetAfterTest();
1527         // First run cron so there are no messages waiting to be sent (from other tests).
1528         cron_setup_user();
1529         assign::cron();
1531         $course = $this->getDataGenerator()->create_course();
1532         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1533         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1535         // Now create an assignment and add some feedback.
1536         $this->setUser($teacher);
1537         $assign = $this->create_instance($course, [
1538                 'sendstudentnotifications' => 1,
1539             ]);
1541         $this->add_submission($student, $assign);
1542         $this->submit_for_grading($student, $assign);
1543         $this->mark_submission($teacher, $assign, $student, 50.0, [
1544                 'sendstudentnotifications' => 0,
1545             ]);
1547         cron_setup_user();
1548         $sink = $this->redirectMessages();
1549         assign::cron();
1550         $messages = $sink->get_messages();
1552         $this->assertEquals(0, count($messages));
1553     }
1555     public function test_cron_regraded() {
1556         $this->resetAfterTest();
1558         // First run cron so there are no messages waiting to be sent (from other tests).
1559         cron_setup_user();
1560         assign::cron();
1562         $course = $this->getDataGenerator()->create_course();
1563         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1564         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1566         // Now create an assignment and add some feedback.
1567         $this->setUser($teacher);
1568         $assign = $this->create_instance($course, [
1569                 'sendstudentnotifications' => 1,
1570             ]);
1572         $this->add_submission($student, $assign);
1573         $this->submit_for_grading($student, $assign);
1574         $this->mark_submission($teacher, $assign, $student, 50.0);
1576         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1577         cron_setup_user();
1578         assign::cron();
1580         // Regrade.
1581         $this->mark_submission($teacher, $assign, $student, 50.0);
1583         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1584         cron_setup_user();
1585         $sink = $this->redirectMessages();
1586         assign::cron();
1587         $messages = $sink->get_messages();
1589         $this->assertEquals(1, count($messages));
1590         $this->assertEquals(1, $messages[0]->notification);
1591         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1592     }
1594     /**
1595      * Test delivery of grade notifications as controlled by marking workflow.
1596      */
1597     public function test_markingworkflow_cron() {
1598         $this->resetAfterTest();
1600         // First run cron so there are no messages waiting to be sent (from other tests).
1601         cron_setup_user();
1602         assign::cron();
1604         $course = $this->getDataGenerator()->create_course();
1605         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1606         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1608         // Now create an assignment and add some feedback.
1609         $this->setUser($teacher);
1610         $assign = $this->create_instance($course, [
1611                 'sendstudentnotifications' => 1,
1612                 'markingworkflow' => 1,
1613             ]);
1615         // Mark a submission but set the workflowstate to an unreleased state.
1616         // This should not trigger a notification.
1617         $this->add_submission($student, $assign);
1618         $this->submit_for_grading($student, $assign);
1619         $this->mark_submission($teacher, $assign, $student, 50.0, [
1620                 'sendstudentnotifications' => 1,
1621                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1622             ]);
1624         cron_setup_user();
1625         $sink = $this->redirectMessages();
1626         assign::cron();
1627         $messages = $sink->get_messages();
1629         $this->assertEquals(0, count($messages));
1631         // Transition to the released state.
1632         $this->setUser($teacher);
1633         $submission = $assign->get_user_submission($student->id, true);
1634         $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1635         $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1637         // Now run cron and see that one message was sent.
1638         cron_setup_user();
1639         $sink = $this->redirectMessages();
1640         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1641         assign::cron();
1642         $messages = $sink->get_messages();
1644         $this->assertEquals(1, count($messages));
1645         $this->assertEquals(1, $messages[0]->notification);
1646         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1647     }
1649     public function test_cron_message_includes_courseid() {
1650         $this->resetAfterTest();
1652         // First run cron so there are no messages waiting to be sent (from other tests).
1653         cron_setup_user();
1654         assign::cron();
1656         $course = $this->getDataGenerator()->create_course();
1657         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1658         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1660         // Now create an assignment and add some feedback.
1661         $this->setUser($teacher);
1662         $assign = $this->create_instance($course, [
1663                 'sendstudentnotifications' => 1,
1664             ]);
1666         // Mark a submission but set the workflowstate to an unreleased state.
1667         // This should not trigger a notification.
1668         $this->add_submission($student, $assign);
1669         $this->submit_for_grading($student, $assign);
1670         $this->mark_submission($teacher, $assign, $student);
1671         phpunit_util::stop_message_redirection();
1673         // Now run cron and see that one message was sent.
1674         cron_setup_user();
1675         $this->preventResetByRollback();
1676         $sink = $this->redirectEvents();
1677         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1678         assign::cron();
1680         $events = $sink->get_events();
1681         $event = reset($events);
1682         $this->assertInstanceOf('\core\event\notification_sent', $event);
1683         $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1684         $sink->close();
1685     }
1687     public function test_is_graded() {
1688         $this->resetAfterTest();
1690         $course = $this->getDataGenerator()->create_course();
1691         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1692         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1693         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1695         $assign = $this->create_instance($course);
1697         $this->add_submission($student, $assign);
1698         $this->submit_for_grading($student, $assign);
1699         $this->mark_submission($teacher, $assign, $student, 50.0);
1701         $this->setUser($teacher);
1702         $this->assertEquals(true, $assign->testable_is_graded($student->id));
1703         $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1704     }
1706     public function test_can_grade() {
1707         global $DB;
1709         $this->resetAfterTest();
1711         $course = $this->getDataGenerator()->create_course();
1712         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1713         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1715         $assign = $this->create_instance($course);
1717         $this->setUser($student);
1718         $this->assertEquals(false, $assign->can_grade());
1720         $this->setUser($teacher);
1721         $this->assertEquals(true, $assign->can_grade());
1723         // Test the viewgrades capability - without mod/assign:grade.
1724         $this->setUser($student);
1726         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1727         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1728         $this->assertEquals(false, $assign->can_grade());
1729     }
1731     public function test_can_view_submission() {
1732         global $DB;
1734         $this->resetAfterTest();
1736         $course = $this->getDataGenerator()->create_course();
1737         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1738         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1739         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1740         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1741         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1743         $assign = $this->create_instance($course);
1745         $this->setUser($student);
1746         $this->assertEquals(true, $assign->can_view_submission($student->id));
1747         $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1748         $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1750         $this->setUser($teacher);
1751         $this->assertEquals(true, $assign->can_view_submission($student->id));
1752         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1753         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1754         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1756         $this->setUser($editingteacher);
1757         $this->assertEquals(true, $assign->can_view_submission($student->id));
1758         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1759         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1760         $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1762         // Test the viewgrades capability - without mod/assign:grade.
1763         $this->setUser($student);
1764         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1765         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
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));
1770     }
1772     public function test_update_submission() {
1773         $this->resetAfterTest();
1775         $course = $this->getDataGenerator()->create_course();
1776         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1777         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1779         $assign = $this->create_instance($course);
1781         $this->add_submission($student, $assign);
1782         $submission = $assign->get_user_submission($student->id, 0);
1783         $assign->testable_update_submission($submission, $student->id, true, true);
1785         $this->setUser($teacher);
1787         // Verify the gradebook update.
1788         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1790         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1791         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1792     }
1794     public function test_update_submission_team() {
1795         $this->resetAfterTest();
1797         $course = $this->getDataGenerator()->create_course();
1798         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1799         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1801         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1802         groups_add_member($group, $student);
1804         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1805         groups_add_member($group, $otherstudent);
1807         $assign = $this->create_instance($course, [
1808                 'teamsubmission' => 1,
1809             ]);
1811         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1812         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1813         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1815         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1816         $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1817         $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1819         $this->add_submission($student, $assign);
1820         $submission = $assign->get_group_submission($student->id, 0, true);
1821         $assign->testable_update_submission($submission, $student->id, true, true);
1823         // Verify the gradebook update for the student.
1824         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1826         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1827         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1829         // Verify the gradebook update for the other student.
1830         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1832         $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1833         $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1834     }
1836     public function test_update_submission_suspended() {
1837         $this->resetAfterTest();
1839         $course = $this->getDataGenerator()->create_course();
1840         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1841         $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1843         $assign = $this->create_instance($course);
1845         $this->add_submission($student, $assign);
1846         $submission = $assign->get_user_submission($student->id, 0);
1847         $assign->testable_update_submission($submission, $student->id, true, false);
1849         $this->setUser($teacher);
1851         // Verify the gradebook update.
1852         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1854         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1855         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1856     }
1858     public function test_update_submission_blind() {
1859         $this->resetAfterTest();
1861         $course = $this->getDataGenerator()->create_course();
1862         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1863         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1865         $assign = $this->create_instance($course, [
1866                 'blindmarking' => 1,
1867             ]);
1869         $this->add_submission($student, $assign);
1870         $submission = $assign->get_user_submission($student->id, 0);
1871         $assign->testable_update_submission($submission, $student->id, true, false);
1873         // Verify the gradebook update.
1874         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1876         // The usermodified is not set because this is blind marked.
1877         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1878         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1879     }
1881     public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
1882         global $PAGE;
1884         $this->resetAfterTest();
1886         $course = $this->getDataGenerator()->create_course();
1887         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1888         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1890         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1891         groups_add_member($group, $student);
1893         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1894         groups_add_member($group, $otherstudent);
1896         $assign = $this->create_instance($course, [
1897                 'teamsubmission' => 1,
1898                 'assignsubmission_onlinetext_enabled' => 1,
1899                 'submissiondrafts' => 1,
1900                 'requireallteammemberssubmit' => 1,
1901             ]);
1903         // Now verify group assignments.
1904         $this->setUser($teacher);
1905         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1907         // Add a submission.
1908         $this->add_submission($student, $assign);
1910         // Check we can see the submit button.
1911         $this->setUser($student);
1912         $output = $assign->view_student_summary($student, true);
1913         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1915         $submission = $assign->get_group_submission($student->id, 0, true);
1916         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1917         $assign->testable_update_submission($submission, $student->id, true, true);
1919         // Check that the student does not see "Submit" button.
1920         $output = $assign->view_student_summary($student, true);
1921         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1923         // Change to another user in the same group.
1924         $this->setUser($otherstudent);
1925         $output = $assign->view_student_summary($otherstudent, true);
1926         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1928         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
1929         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1930         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
1931         $output = $assign->view_student_summary($otherstudent, true);
1932         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1933     }
1935     public function test_group_submissions_submit_for_marking() {
1936         global $PAGE;
1938         $this->resetAfterTest();
1940         $course = $this->getDataGenerator()->create_course();
1941         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1942         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1944         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1945         groups_add_member($group, $student);
1947         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1948         groups_add_member($group, $otherstudent);
1950         // Now verify group assignments.
1951         $this->setUser($teacher);
1952         $time = time();
1953         $assign = $this->create_instance($course, [
1954                 'teamsubmission' => 1,
1955                 'assignsubmission_onlinetext_enabled' => 1,
1956                 'submissiondrafts' => 1,
1957                 'requireallteammemberssubmit' => 0,
1958                 'duedate' => $time - (2 * DAYSECS),
1959             ]);
1960         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1962         // Add a submission.
1963         $this->add_submission($student, $assign);
1966         // Check we can see the submit button.
1967         $output = $assign->view_student_summary($student, true);
1968         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1969         $this->assertContains(get_string('timeremaining', 'assign'), $output);
1970         $difftime = time() - $time;
1971         $this->assertContains(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
1973         $submission = $assign->get_group_submission($student->id, 0, true);
1974         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1975         $assign->testable_update_submission($submission, $student->id, true, true);
1977         // Check that the student does not see "Submit" button.
1978         $output = $assign->view_student_summary($student, true);
1979         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1981         // Change to another user in the same group.
1982         $this->setUser($otherstudent);
1983         $output = $assign->view_student_summary($otherstudent, true);
1984         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1986         // Check that time remaining is not overdue.
1987         $this->assertContains(get_string('timeremaining', 'assign'), $output);
1988         $difftime = time() - $time;
1989         $this->assertContains(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
1991         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
1992         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1993         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
1994         $output = $assign->view_student_summary($otherstudent, true);
1995         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1996     }
1998     public function test_submissions_open() {
1999         global $DB;
2001         $this->resetAfterTest();
2003         $course = $this->getDataGenerator()->create_course();
2004         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2005         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2006         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2007         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2008         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
2010         $this->setAdminUser();
2012         $now = time();
2013         $tomorrow = $now + DAYSECS;
2014         $oneweek = $now + WEEKSECS;
2015         $yesterday = $now - DAYSECS;
2017         $assign = $this->create_instance($course);
2018         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2020         $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
2021         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2023         $assign = $this->create_instance($course, ['duedate' => $yesterday]);
2024         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2026         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
2027         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2029         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2030         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2032         $assign->testable_save_user_extension($student->id, $tomorrow);
2033         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2035         $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2036         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2038         $this->setUser($student);
2039         $submission = $assign->get_user_submission($student->id, true);
2040         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2041         $assign->testable_update_submission($submission, $student->id, true, false);
2043         $this->setUser($teacher);
2044         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2045     }
2047     public function test_get_graders() {
2048         global $DB;
2050         $this->resetAfterTest();
2052         $course = $this->getDataGenerator()->create_course();
2053         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2054         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2055         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2057         $this->setAdminUser();
2059         // Create an assignment with no groups.
2060         $assign = $this->create_instance($course);
2061         $this->assertCount(2, $assign->testable_get_graders($student->id));
2062     }
2064     public function test_get_graders_separate_groups() {
2065         global $DB;
2067         $this->resetAfterTest();
2069         $course = $this->getDataGenerator()->create_course();
2070         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2071         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2072         $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2073         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2074         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2075         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2077         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2078         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2079         groups_add_member($group1, $student);
2081         $this->setAdminUser();
2083         // Force create an assignment with SEPARATEGROUPS.
2084         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2085         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2087         $assign = $this->create_instance($course, [
2088                 'groupingid' => $grouping->id,
2089                 'groupmode' => SEPARATEGROUPS,
2090             ]);
2092         $this->assertCount(4, $assign->testable_get_graders($student->id));
2094         // Note the second student is in a group that is not in the grouping.
2095         // This means that we get all graders that are not in a group in the grouping.
2096         $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2097     }
2099     public function test_get_notified_users() {
2100         global $CFG, $DB;
2102         $this->resetAfterTest();
2104         $course = $this->getDataGenerator()->create_course();
2105         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2106         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2107         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2109         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2110         groups_add_member($group1, $teacher);
2112         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2113         groups_add_member($group1, $editingteacher);
2115         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2116         groups_add_member($group1, $student);
2118         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2119         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2121         $capability = 'mod/assign:receivegradernotifications';
2122         $coursecontext = context_course::instance($course->id);
2123         $role = $DB->get_record('role', array('shortname' => 'teacher'));
2125         $this->setUser($teacher);
2127         // Create an assignment with no groups.
2128         $assign = $this->create_instance($course);
2130         $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2132         // Change nonediting teachers role to not receive grader notifications.
2133         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2135         // Only the editing teachers will be returned.
2136         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2138         // Note the second student is in a group that is not in the grouping.
2139         // This means that we get all graders that are not in a group in the grouping.
2140         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2141     }
2143     public function test_get_notified_users_in_grouping() {
2144         global $CFG, $DB;
2146         $this->resetAfterTest();
2148         $course = $this->getDataGenerator()->create_course();
2149         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2150         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2151         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2153         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2154         groups_add_member($group1, $teacher);
2156         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2157         groups_add_member($group1, $editingteacher);
2159         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2160         groups_add_member($group1, $student);
2162         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2163         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2165         // Force create an assignment with SEPARATEGROUPS.
2166         $assign = $this->create_instance($course, [
2167                 'groupingid' => $grouping->id,
2168                 'groupmode' => SEPARATEGROUPS,
2169             ]);
2171         // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2172         $this->setUser($student);
2173         $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2175         // Note the second student is in a group that is not in the grouping.
2176         // This means that we get all graders that are not in a group in the grouping.
2177         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2179         // Change nonediting teachers role to not receive grader notifications.
2180         $capability = 'mod/assign:receivegradernotifications';
2181         $coursecontext = context_course::instance($course->id);
2182         $role = $DB->get_record('role', ['shortname' => 'teacher']);
2183         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2185         // Only the editing teachers will be returned.
2186         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2188         // Note the second student is in a group that is not in the grouping.
2189         // This means that we get all graders that are not in a group in the grouping.
2190         // Unfortunately there are no editing teachers who are not in a group.
2191         $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2192     }
2194     public function test_group_members_only() {
2195         global $CFG;
2197         $this->resetAfterTest();
2199         $course = $this->getDataGenerator()->create_course();
2200         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2201         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2202         $this->getDataGenerator()->create_grouping_group([
2203                 'groupid' => $group1->id,
2204                 'groupingid' => $grouping->id,
2205             ]);
2207         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2208         $this->getDataGenerator()->create_grouping_group([
2209                 'groupid' => $group2->id,
2210                 'groupingid' => $grouping->id,
2211             ]);
2213         $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2215         // Add users in the following groups
2216         // - Teacher - Group 1.
2217         // - Student - Group 1.
2218         // - Student - Group 2.
2219         // - Student - Unrelated Group
2220         // - Student - No group.
2221         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2222         groups_add_member($group1, $teacher);
2224         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2225         groups_add_member($group1, $student);
2227         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2228         groups_add_member($group2, $otherstudent);
2230         $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2231         groups_add_member($group2, $otherstudent);
2233         $this->getDataGenerator()->create_and_enrol($course, 'student');
2235         $this->setAdminUser();
2237         $CFG->enableavailability = true;
2238         $assign = $this->create_instance($course, [], [
2239                 'availability' => json_encode(
2240                     \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2241                 ),
2242                 'groupingid' => $grouping->id,
2243             ]);
2245         // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2246         // group, or the student in an unrelated group.
2247         $this->setUser($teacher);
2248         $participants = $assign->list_participants(0, true);
2249         $this->assertCount(2, $participants);
2250         $this->assertTrue(isset($participants[$student->id]));
2251         $this->assertTrue(isset($participants[$otherstudent->id]));
2252     }
2254     public function test_get_uniqueid_for_user() {
2255         $this->resetAfterTest();
2257         $course = $this->getDataGenerator()->create_course();
2258         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2259         $students = [];
2260         for ($i = 0; $i < 10; $i++) {
2261             $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2262             $students[$student->id] = $student;
2263         }
2265         $this->setUser($teacher);
2266         $assign = $this->create_instance($course);
2268         foreach ($students as $student) {
2269             $uniqueid = $assign->get_uniqueid_for_user($student->id);
2270             $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2271         }
2272     }
2274     public function test_show_student_summary() {
2275         global $CFG, $PAGE;
2277         $this->resetAfterTest();
2279         $course = $this->getDataGenerator()->create_course();
2280         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2281         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2282         $this->setUser($teacher);
2283         $assign = $this->create_instance($course);
2284         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2286         // No feedback should be available because this student has not been graded.
2287         $this->setUser($student);
2288         $output = $assign->view_student_summary($student, true);
2289         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if there is no grade');
2291         // Simulate adding a grade.
2292         $this->add_submission($student, $assign);
2293         $this->submit_for_grading($student, $assign);
2294         $this->mark_submission($teacher, $assign, $student);
2296         // Now we should see the feedback.
2297         $this->setUser($student);
2298         $output = $assign->view_student_summary($student, true);
2299         $this->assertRegexp('/Feedback/', $output, 'Show feedback if there is a grade');
2301         // Now hide the grade in gradebook.
2302         $this->setUser($teacher);
2303         require_once($CFG->libdir.'/gradelib.php');
2304         $gradeitem = new grade_item(array(
2305             'itemtype'      => 'mod',
2306             'itemmodule'    => 'assign',
2307             'iteminstance'  => $assign->get_instance()->id,
2308             'courseid'      => $course->id));
2310         $gradeitem->set_hidden(1, false);
2312         // No feedback should be available because the grade is hidden.
2313         $this->setUser($student);
2314         $output = $assign->view_student_summary($student, true);
2315         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2316     }
2318     public function test_show_student_summary_with_feedback() {
2319         global $CFG, $PAGE;
2321         $this->resetAfterTest();
2323         $course = $this->getDataGenerator()->create_course();
2324         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2325         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2326         $this->setUser($teacher);
2327         $assign = $this->create_instance($course, [
2328                 'assignfeedback_comments_enabled' => 1
2329             ]);
2330         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2332         // No feedback should be available because this student has not been graded.
2333         $this->setUser($student);
2334         $output = $assign->view_student_summary($student, true);
2335         $this->assertNotRegexp('/Feedback/', $output);
2337         // Simulate adding a grade.
2338         $this->add_submission($student, $assign);
2339         $this->submit_for_grading($student, $assign);
2340         $this->mark_submission($teacher, $assign, $student, null, [
2341                 'assignfeedbackcomments_editor' => [
2342                     'text' => 'Tomato sauce',
2343                     'format' => FORMAT_MOODLE,
2344                 ],
2345             ]);
2347         // Should have feedback but no grade.
2348         $this->setUser($student);
2349         $output = $assign->view_student_summary($student, true);
2350         $this->assertRegexp('/Feedback/', $output);
2351         $this->assertRegexp('/Tomato sauce/', $output);
2352         $this->assertNotRegexp('/Grade/', $output, 'Do not show grade when there is no grade.');
2353         $this->assertNotRegexp('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2355         // Add a grade now.
2356         $this->mark_submission($teacher, $assign, $student, 50.0, [
2357                 'assignfeedbackcomments_editor' => [
2358                     'text' => 'Bechamel sauce',
2359                     'format' => FORMAT_MOODLE,
2360                 ],
2361             ]);
2363         // Should have feedback but no grade.
2364         $this->setUser($student);
2365         $output = $assign->view_student_summary($student, true);
2366         $this->assertNotRegexp('/Tomato sauce/', $output);
2367         $this->assertRegexp('/Bechamel sauce/', $output);
2368         $this->assertRegexp('/Grade/', $output);
2369         $this->assertRegexp('/Graded on/', $output);
2371         // Now hide the grade in gradebook.
2372         $this->setUser($teacher);
2373         $gradeitem = new grade_item(array(
2374             'itemtype'      => 'mod',
2375             'itemmodule'    => 'assign',
2376             'iteminstance'  => $assign->get_instance()->id,
2377             'courseid'      => $course->id));
2379         $gradeitem->set_hidden(1, false);
2381         // No feedback should be available because the grade is hidden.
2382         $this->setUser($student);
2383         $output = $assign->view_student_summary($student, true);
2384         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2385     }
2387     /**
2388      * Test reopen behavior when in "Manual" mode.
2389      */
2390     public function test_attempt_reopen_method_manual() {
2391         global $PAGE;
2393         $this->resetAfterTest();
2394         $course = $this->getDataGenerator()->create_course();
2395         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2396         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2398         $assign = $this->create_instance($course, [
2399                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2400                 'maxattempts' => 3,
2401                 'submissiondrafts' => 1,
2402                 'assignsubmission_onlinetext_enabled' => 1,
2403             ]);
2404         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2406         // Student should be able to see an add submission button.
2407         $this->setUser($student);
2408         $output = $assign->view_student_summary($student, true);
2409         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2411         // Add a submission.
2412         $this->add_submission($student, $assign);
2413         $this->submit_for_grading($student, $assign);
2415         // Verify the student cannot make changes to the submission.
2416         $output = $assign->view_student_summary($student, true);
2417         $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2419         // Mark the submission.
2420         $this->mark_submission($teacher, $assign, $student);
2422         // Check the student can see the grade.
2423         $this->setUser($student);
2424         $output = $assign->view_student_summary($student, true);
2425         $this->assertNotEquals(false, strpos($output, '50.0'));
2427         // Allow the student another attempt.
2428         $teacher->ignoresesskey = true;
2429         $this->setUser($teacher);
2430         $result = $assign->testable_process_add_attempt($student->id);
2431         $this->assertEquals(true, $result);
2433         // Check that the previous attempt is now in the submission history table.
2434         $this->setUser($student);
2435         $output = $assign->view_student_summary($student, true);
2436         // Need a better check.
2437         $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2439         // Check that the student now has a button for Add a new attempt".
2440         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2441         // Check that the student now does not have a button for Submit.
2442         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2444         // Check that the student now has a submission history.
2445         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2447         $this->setUser($teacher);
2448         // Check that the grading table loads correctly and contains this user.
2449         // This is also testing that we do not get duplicate rows in the grading table.
2450         $gradingtable = new assign_grading_table($assign, 100, '', 0, true);
2451         $output = $assign->get_renderer()->render($gradingtable);
2452         $this->assertEquals(true, strpos($output, $student->lastname));
2454         // Should be 1 not 2.
2455         $this->assertEquals(1, $assign->count_submissions());
2456         $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2457         $this->assertEquals(0, $assign->count_submissions_need_grading());
2458         $this->assertEquals(1, $assign->count_grades());
2460         // Change max attempts to unlimited.
2461         $formdata = clone($assign->get_instance());
2462         $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2463         $formdata->instance = $formdata->id;
2464         $assign->update_instance($formdata);
2466         // Mark the submission again.
2467         $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2469         // Check the grade exists.
2470         $this->setUser($teacher);
2471         $grades = $assign->get_user_grades_for_gradebook($student->id);
2472         $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2474         // Check we can reopen still.
2475         $result = $assign->testable_process_add_attempt($student->id);
2476         $this->assertEquals(true, $result);
2478         // Should no longer have a grade because there is no grade for the latest attempt.
2479         $grades = $assign->get_user_grades_for_gradebook($student->id);
2480         $this->assertEmpty($grades);
2481     }
2483     /**
2484      * Test reopen behavior when in "Reopen until pass" mode.
2485      */
2486     public function test_attempt_reopen_method_untilpass() {
2487         global $PAGE;
2489         $this->resetAfterTest();
2490         $course = $this->getDataGenerator()->create_course();
2491         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2492         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2494         $assign = $this->create_instance($course, [
2495                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2496                 'maxattempts' => 3,
2497                 'submissiondrafts' => 1,
2498                 'assignsubmission_onlinetext_enabled' => 1,
2499             ]);
2500         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2502         // Set grade to pass to 80.
2503         $gradeitem = $assign->get_grade_item();
2504         $gradeitem->gradepass = '80.0';
2505         $gradeitem->update();
2507         // Student should be able to see an add submission button.
2508         $this->setUser($student);
2509         $output = $assign->view_student_summary($student, true);
2510         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2512         // Add a submission.
2513         $this->add_submission($student, $assign);
2514         $this->submit_for_grading($student, $assign);
2516         // Verify the student cannot make a new attempt.
2517         $output = $assign->view_student_summary($student, true);
2518         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2520         // Mark the submission as non-passing.
2521         $this->mark_submission($teacher, $assign, $student, 50.0);
2523         // Check the student can see the grade.
2524         $this->setUser($student);
2525         $output = $assign->view_student_summary($student, true);
2526         $this->assertNotEquals(false, strpos($output, '50.0'));
2528         // Check that the student now has a button for Add a new attempt.
2529         $output = $assign->view_student_summary($student, true);
2530         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2532         // Check that the student now does not have a button for Submit.
2533         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2535         // Check that the student now has a submission history.
2536         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2538         // Add a second submission.
2539         $this->add_submission($student, $assign);
2540         $this->submit_for_grading($student, $assign);
2542         // Mark the submission as passing.
2543         $this->mark_submission($teacher, $assign, $student, 80.0);
2545         // Check that the student does not have a button for Add a new attempt.
2546         $this->setUser($student);
2547         $output = $assign->view_student_summary($student, true);
2548         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2550         // Re-mark the submission as not passing.
2551         $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2553         // Check that the student now has a button for Add a new attempt.
2554         $this->setUser($student);
2555         $output = $assign->view_student_summary($student, true);
2556         $this->assertRegexp('/' . get_string('addnewattempt', 'assign') . '/', $output);
2557         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2558     }
2560     public function test_attempt_reopen_method_untilpass_passing() {
2561         global $PAGE;
2563         $this->resetAfterTest();
2564         $course = $this->getDataGenerator()->create_course();
2565         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2566         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2568         $assign = $this->create_instance($course, [
2569                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2570                 'maxattempts' => 3,
2571                 'submissiondrafts' => 1,
2572                 'assignsubmission_onlinetext_enabled' => 1,
2573             ]);
2574         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2576         // Set grade to pass to 80.
2577         $gradeitem = $assign->get_grade_item();
2578         $gradeitem->gradepass = '80.0';
2579         $gradeitem->update();
2581         // Student should be able to see an add submission button.
2582         $this->setUser($student);
2583         $output = $assign->view_student_summary($student, true);
2584         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2586         // Add a submission as a student.
2587         $this->add_submission($student, $assign);
2588         $this->submit_for_grading($student, $assign);
2590         // Mark the submission as passing.
2591         $this->mark_submission($teacher, $assign, $student, 100.0);
2593         // Check the student can see the grade.
2594         $this->setUser($student);
2595         $output = $assign->view_student_summary($student, true);
2596         $this->assertNotEquals(false, strpos($output, '100.0'));
2598         // Check that the student does not have a button for Add a new attempt.
2599         $output = $assign->view_student_summary($student, true);
2600         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2601     }
2603     public function test_attempt_reopen_method_untilpass_no_passing_requirement() {
2604         global $PAGE;
2606         $this->resetAfterTest();
2607         $course = $this->getDataGenerator()->create_course();
2608         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2609         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2611         $assign = $this->create_instance($course, [
2612                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2613                 'maxattempts' => 3,
2614                 'submissiondrafts' => 1,
2615                 'assignsubmission_onlinetext_enabled' => 1,
2616             ]);
2617         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2619         // Set grade to pass to 0, so that no attempts should reopen.
2620         $gradeitem = $assign->get_grade_item();
2621         $gradeitem->gradepass = '0';
2622         $gradeitem->update();
2624         // Student should be able to see an add submission button.
2625         $this->setUser($student);
2626         $output = $assign->view_student_summary($student, true);
2627         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2629         // Add a submission.
2630         $this->add_submission($student, $assign);
2631         $this->submit_for_grading($student, $assign);
2633         // Mark the submission with any grade.
2634         $this->mark_submission($teacher, $assign, $student, 0.0);
2636         // Check the student can see the grade.
2637         $this->setUser($student);
2638         $output = $assign->view_student_summary($student, true);
2639         $this->assertNotEquals(false, strpos($output, '0.0'));
2641         // Check that the student does not have a button for Add a new attempt.
2642         $output = $assign->view_student_summary($student, true);
2643         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2644     }
2646     /**
2647      * Test student visibility for each stage of the marking workflow.
2648      */
2649     public function test_markingworkflow() {
2650         global $PAGE;
2652         $this->resetAfterTest();
2653         $course = $this->getDataGenerator()->create_course();
2654         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2655         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2657         $assign = $this->create_instance($course, [
2658                 'markingworkflow' => 1,
2659             ]);
2661         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2663         // Mark the submission and set to notmarked.
2664         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2665                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2666             ]);
2668         // Check the student can't see the grade.
2669         $this->setUser($student);
2670         $output = $assign->view_student_summary($student, true);
2671         $this->assertEquals(false, strpos($output, '50.0'));
2673         // Make sure the grade isn't pushed to the gradebook.
2674         $grades = $assign->get_user_grades_for_gradebook($student->id);
2675         $this->assertEmpty($grades);
2677         // Mark the submission and set to inmarking.
2678         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2679                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2680             ]);
2682         // Check the student can't see the grade.
2683         $this->setUser($student);
2684         $output = $assign->view_student_summary($student, true);
2685         $this->assertEquals(false, strpos($output, '50.0'));
2687         // Make sure the grade isn't pushed to the gradebook.
2688         $grades = $assign->get_user_grades_for_gradebook($student->id);
2689         $this->assertEmpty($grades);
2691         // Mark the submission and set to readyforreview.
2692         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2693                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2694             ]);
2696         // Check the student can't see the grade.
2697         $this->setUser($student);
2698         $output = $assign->view_student_summary($student, true);
2699         $this->assertEquals(false, strpos($output, '50.0'));
2701         // Make sure the grade isn't pushed to the gradebook.
2702         $grades = $assign->get_user_grades_for_gradebook($student->id);
2703         $this->assertEmpty($grades);
2705         // Mark the submission and set to inreview.
2706         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2707                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2708             ]);
2710         // Check the student can't see the grade.
2711         $this->setUser($student);
2712         $output = $assign->view_student_summary($student, true);
2713         $this->assertEquals(false, strpos($output, '50.0'));
2715         // Make sure the grade isn't pushed to the gradebook.
2716         $grades = $assign->get_user_grades_for_gradebook($student->id);
2717         $this->assertEmpty($grades);
2719         // Mark the submission and set to readyforrelease.
2720         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2721                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
2722             ]);
2724         // Check the student can't see the grade.
2725         $this->setUser($student);
2726         $output = $assign->view_student_summary($student, true);
2727         $this->assertEquals(false, strpos($output, '50.0'));
2729         // Make sure the grade isn't pushed to the gradebook.
2730         $grades = $assign->get_user_grades_for_gradebook($student->id);
2731         $this->assertEmpty($grades);
2733         // Mark the submission and set to released.
2734         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2735                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
2736             ]);
2738         // Check the student can see the grade.
2739         $this->setUser($student);
2740         $output = $assign->view_student_summary($student, true);
2741         $this->assertNotEquals(false, strpos($output, '50.0'));
2743         // Make sure the grade is pushed to the gradebook.
2744         $grades = $assign->get_user_grades_for_gradebook($student->id);
2745         $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
2746     }
2748     /**
2749      * Test that a student allocated a specific marker is only shown to that marker.
2750      */
2751     public function test_markerallocation() {
2752         global $PAGE;
2754         $this->resetAfterTest();
2755         $course = $this->getDataGenerator()->create_course();
2756         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2757         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2758         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2760         $assign = $this->create_instance($course, [
2761                 'markingworkflow' => 1,
2762                 'markingallocation' => 1
2763             ]);
2765         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2767         // Allocate marker to submission.
2768         $this->mark_submission($teacher, $assign, $student, null, [
2769             'allocatedmarker' => $teacher->id,
2770         ]);
2772         // Check the allocated marker can view the submission.
2773         $this->setUser($teacher);
2774         $users = $assign->list_participants(0, true);
2775         $this->assertEquals(1, count($users));
2776         $this->assertTrue(isset($users[$student->id]));
2778         $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2779         $context = context_module::instance($cm->id);
2780         $assign = new mod_assign_testable_assign($context, $cm, $course);
2782         // Check that other teachers can't view this submission.
2783         $this->setUser($otherteacher);
2784         $users = $assign->list_participants(0, true);
2785         $this->assertEquals(0, count($users));
2786     }
2788     /**
2789      * Ensure that a teacher cannot submit for students as standard.
2790      */
2791     public function test_teacher_submit_for_student() {
2792         global $PAGE;
2794         $this->resetAfterTest();
2795         $course = $this->getDataGenerator()->create_course();
2796         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2797         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2799         $assign = $this->create_instance($course, [
2800             'assignsubmission_onlinetext_enabled' => 1,
2801             'submissiondrafts' => 1,
2802         ]);
2804         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2806         // Add a submission but do not submit.
2807         $this->add_submission($student, $assign, 'Student submission text');
2809         $this->setUser($student);
2810         $output = $assign->view_student_summary($student, true);
2811         $this->assertContains('Student submission text', $output, 'Contains student submission text');
2813         // Check that a teacher can not edit the submission as they do not have the capability.
2814         $this->setUser($teacher);
2815         $this->expectException('moodle_exception');
2816         $this->expectExceptionMessage('error/nopermission');
2817         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2818     }
2820     /**
2821      * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
2822      */
2823     public function test_teacher_submit_for_student_with_capability() {
2824         global $PAGE;
2826         $this->resetAfterTest();
2827         $course = $this->getDataGenerator()->create_course();
2828         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2829         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2830         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2832         $assign = $this->create_instance($course, [
2833             'assignsubmission_onlinetext_enabled' => 1,
2834             'submissiondrafts' => 1,
2835         ]);
2837         // Add the required capability.
2838         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2839         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2840         role_assign($roleid, $teacher->id, $assign->get_context()->id);
2841         accesslib_clear_all_caches_for_unit_testing();
2843         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2845         // Add a submission but do not submit.
2846         $this->add_submission($student, $assign, 'Student submission text');
2848         $this->setUser($student);
2849         $output = $assign->view_student_summary($student, true);
2850         $this->assertContains('Student submission text', $output, 'Contains student submission text');
2852         // Check that a teacher can edit the submission.
2853         $this->setUser($teacher);
2854         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2856         $this->setUser($student);
2857         $output = $assign->view_student_summary($student, true);
2858         $this->assertNotContains('Student submission text', $output, 'Contains student submission text');
2859         $this->assertContains('Teacher edited submission text', $output, 'Contains teacher edited submission text');
2861         // Check that the teacher can submit the students work.
2862         $this->setUser($teacher);
2863         $this->submit_for_grading($student, $assign, [], false);
2865         // Revert to draft so the student can edit it.
2866         $assign->revert_to_draft($student->id);
2868         $this->setUser($student);
2870         // Check that the submission text was saved.
2871         $output = $assign->view_student_summary($student, true);
2872         $this->assertContains('Teacher edited submission text', $output, 'Contains student submission text');
2874         // Check that the student can submit their work.
2875         $this->submit_for_grading($student, $assign, []);
2877         $output = $assign->view_student_summary($student, true);
2878         $this->assertNotContains(get_string('addsubmission', 'assign'), $output);
2880         // An editing teacher without the extra role should still be able to revert to draft.
2881         $this->setUser($otherteacher);
2883         // Revert to draft so the submission is editable.
2884         $assign->revert_to_draft($student->id);
2885     }
2887     /**
2888      * Ensure that disabling submit after the cutoff date works as expected.
2889      */
2890     public function test_disable_submit_after_cutoff_date() {
2891         global $PAGE;
2893         $this->resetAfterTest();
2894         $course = $this->getDataGenerator()->create_course();
2895         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2897         $now = time();
2898         $tomorrow = $now + DAYSECS;
2899         $lastweek = $now - (7 * DAYSECS);
2900         $yesterday = $now - DAYSECS;
2902         $this->setAdminUser();
2903         $assign = $this->create_instance($course, [
2904                 'duedate' => $yesterday,
2905                 'cutoffdate' => $tomorrow,
2906                 'assignsubmission_onlinetext_enabled' => 1,
2907             ]);
2909         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2911         // Student should be able to see an add submission button.
2912         $this->setUser($student);
2913         $output = $assign->view_student_summary($student, true);
2914         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2916         // Add a submission but don't submit now.
2917         $this->add_submission($student, $assign);
2919         // Create another instance with cut-off and due-date already passed.
2920         $this->setAdminUser();
2921         $assign = $this->create_instance($course, [
2922                 'duedate' => $lastweek,
2923                 'cutoffdate' => $yesterday,
2924                 'assignsubmission_onlinetext_enabled' => 1,
2925             ]);
2927         $this->setUser($student);
2928         $output = $assign->view_student_summary($student, true);
2929         $this->assertNotContains($output, get_string('editsubmission', 'assign'),
2930                                  'Should not be able to edit after cutoff date.');
2931         $this->assertNotContains($output, get_string('submitassignment', 'assign'),
2932                                  'Should not be able to submit after cutoff date.');
2933     }
2935     /**
2936      * Testing for submission comment plugin settings.
2937      *
2938      * @dataProvider submission_plugin_settings_provider
2939      * @param   bool    $globalenabled
2940      * @param   array   $instanceconfig
2941      * @param   bool    $isenabled
2942      */
2943     public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled) {
2944         global $CFG;
2946         $this->resetAfterTest();
2947         $course = $this->getDataGenerator()->create_course();
2949         $CFG->usecomments = $globalenabled;
2950         $assign = $this->create_instance($course, $instanceconfig);
2951         $plugin = $assign->get_submission_plugin_by_type('comments');
2952         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
2953     }
2955     public function submission_plugin_settings_provider() {
2956         return [
2957             'CFG->usecomments true, empty config => Enabled by default' => [
2958                 true,
2959                 [],
2960                 true,
2961             ],
2962             'CFG->usecomments true, config enabled => Comments enabled' => [
2963                 true,
2964                 [
2965                     'assignsubmission_comments_enabled' => 1,
2966                 ],
2967                 true,
2968             ],
2969             'CFG->usecomments true, config idisabled => Comments enabled' => [
2970                 true,
2971                 [
2972                     'assignsubmission_comments_enabled' => 0,
2973                 ],
2974                 true,
2975             ],
2976             'CFG->usecomments false, empty config => Disabled by default' => [
2977                 false,
2978                 [],
2979                 false,
2980             ],
2981             'CFG->usecomments false, config enabled => Comments disabled' => [
2982                 false,
2983                 [
2984                     'assignsubmission_comments_enabled' => 1,
2985                 ],
2986                 false,
2987             ],
2988             'CFG->usecomments false, config disabled => Comments disabled' => [
2989                 false,
2990                 [
2991                     'assignsubmission_comments_enabled' => 0,
2992                 ],
2993                 false,
2994             ],
2995         ];
2996     }
2998     /**
2999      * Testing for comment inline settings
3000      */
3001     public function test_feedback_comment_commentinline() {
3002         global $CFG;
3004         $this->resetAfterTest();
3005         $course = $this->getDataGenerator()->create_course();
3006         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3007         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3009         $sourcetext = "Hello!
3011 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3013 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3014 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3016 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
3017 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
3018 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
3019 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
3020 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
3021 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3022 ";
3024         // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
3025         $filteredtext = "Hello!
3027 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3029 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3030 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3032 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3033 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3034 Internal link 1:
3035 Internal link 2:
3036 Anchor link 1:Link text
3037 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3038 ";
3040         $this->setUser($teacher);
3041         $assign = $this->create_instance($course, [
3042                 'assignsubmission_onlinetext_enabled' => 1,
3043                 'assignfeedback_comments_enabled' => 1,
3044                 'assignfeedback_comments_commentinline' => 1,
3045             ]);
3047         $this->setUser($student);
3049         // Add a submission but don't submit now.
3050         $this->add_submission($student, $assign, $sourcetext);
3052         $this->setUser($teacher);
3054         $data = new stdClass();
3055         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3056         $pagination = [
3057                 'userid' => $student->id,
3058                 'rownum' => 0,
3059                 'last' => true,
3060                 'useridlistid' => $assign->get_useridlist_key_id(),
3061                 'attemptnumber' => 0,
3062             ];
3063         $formparams = array($assign, $data, $pagination);
3064         $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
3066         $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3067     }
3069     /**
3070      * Testing for feedback comment plugin settings.
3071      *
3072      * @dataProvider feedback_plugin_settings_provider
3073      * @param   array   $instanceconfig
3074      * @param   bool    $isenabled
3075      */
3076     public function test_feedback_plugin_settings($instanceconfig, $isenabled) {
3077         $this->resetAfterTest();
3078         $course = $this->getDataGenerator()->create_course();
3080         $assign = $this->create_instance($course, $instanceconfig);
3081         $plugin = $assign->get_feedback_plugin_by_type('comments');
3082         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3083     }
3085     public function feedback_plugin_settings_provider() {
3086         return [
3087             'No configuration => disabled' => [
3088                 [],
3089                 false,
3090             ],
3091             'Actively disabled' => [
3092                 [
3093                     'assignfeedback_comments_enabled' => 0,
3094                 ],
3095                 false,
3096             ],
3097             'Actively enabled' => [
3098                 [
3099                     'assignfeedback_comments_enabled' => 1,
3100                 ],
3101                 true,
3102             ],
3103         ];
3104     }
3106     /**
3107      * Testing if gradebook feedback plugin is enabled.
3108      */
3109     public function test_is_gradebook_feedback_enabled() {
3110         $this->resetAfterTest();
3111         $course = $this->getDataGenerator()->create_course();
3112         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3113         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3115         $adminconfig = get_config('assign');
3116         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3118         // Create assignment with gradebook feedback enabled and grade = 0.
3119         $assign = $this->create_instance($course, [
3120                 "{$gradebookplugin}_enabled" => 1,
3121                 'grades' => 0,
3122             ]);
3124         // Get gradebook feedback plugin.
3125         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3126         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3127         $this->assertEquals(1, $plugin->is_enabled('enabled'));
3128         $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3129     }
3131     /**
3132      * Testing if gradebook feedback plugin is disabled.
3133      */
3134     public function test_is_gradebook_feedback_disabled() {
3135         $this->resetAfterTest();
3136         $course = $this->getDataGenerator()->create_course();
3137         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3138         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3140         $adminconfig = get_config('assign');
3141         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3143         // Create assignment with gradebook feedback disabled and grade = 0.
3144         $assign = $this->create_instance($course, [
3145                 "{$gradebookplugin}_enabled" => 0,
3146                 'grades' => 0,
3147             ]);
3149         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3150         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3151         $this->assertEquals(0, $plugin->is_enabled('enabled'));
3152     }
3154     /**
3155      * Testing can_edit_submission.
3156      */
3157     public function test_can_edit_submission() {
3158         $this->resetAfterTest();
3159         $course = $this->getDataGenerator()->create_course();
3160         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3161         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3162         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3164         $assign = $this->create_instance($course, [
3165                 'assignsubmission_onlinetext_enabled' => 1,
3166                 'submissiondrafts' => 1,
3167             ]);
3169         // Check student can edit their own submission.
3170         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3172         // Check student cannot edit others submission.
3173         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3175         // Check teacher cannot (by default) edit a students submission.
3176         $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3177     }
3179     /**
3180      * Testing can_edit_submission with the editothersubmission capability.
3181      */
3182     public function test_can_edit_submission_with_editothersubmission() {
3183         $this->resetAfterTest();
3184         $course = $this->getDataGenerator()->create_course();
3185         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3186         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3187         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3189         $assign = $this->create_instance($course, [
3190                 'assignsubmission_onlinetext_enabled' => 1,
3191                 'submissiondrafts' => 1,
3192             ]);
3194         // Add the required capability to edit a student submission.
3195         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3196         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3197         role_assign($roleid, $teacher->id, $assign->get_context()->id);
3198         accesslib_clear_all_caches_for_unit_testing();
3200         // Check student can edit their own submission.
3201         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3203         // Check student cannot edit others submission.
3204         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3206         // Retest - should now have access.
3207         $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3208     }
3210     /**
3211      * Testing can_edit_submission
3212      */
3213     public function test_can_edit_submission_separategroups() {
3214         $this->resetAfterTest();
3215         $course = $this->getDataGenerator()->create_course();
3216         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3218         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3219         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3220         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3221         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3223         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3224         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3225         groups_assign_grouping($grouping->id, $group1->id);
3226         groups_add_member($group1, $student1);
3227         groups_add_member($group1, $student2);
3229         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3230         groups_assign_grouping($grouping->id, $group2->id);
3231         groups_add_member($group2, $student3);
3232         groups_add_member($group2, $student4);
3234         $assign = $this->create_instance($course, [
3235                 'assignsubmission_onlinetext_enabled' => 1,
3236                 'submissiondrafts' => 1,
3237                 'groupingid' => $grouping->id,
3238                 'groupmode' => SEPARATEGROUPS,
3239             ]);
3241         // Verify a student does not have the ability to edit submissions for other users.
3242         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3243         $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id));
3244         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3245         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3246     }
3248     /**
3249      * Testing can_edit_submission
3250      */
3251     public function test_can_edit_submission_separategroups_with_editothersubmission() {
3252         $this->resetAfterTest();
3253         $course = $this->getDataGenerator()->create_course();
3254         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3256         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3257         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3258         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3259         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3261         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3262         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3263         groups_assign_grouping($grouping->id, $group1->id);
3264         groups_add_member($group1, $student1);
3265         groups_add_member($group1, $student2);
3267         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3268         groups_assign_grouping($grouping->id, $group2->id);
3269         groups_add_member($group2, $student3);
3270         groups_add_member($group2, $student4);
3272         $assign = $this->create_instance($course, [
3273                 'assignsubmission_onlinetext_enabled' => 1,
3274                 'submissiondrafts' => 1,
3275                 'groupingid' => $grouping->id,
3276                 'groupmode' => SEPARATEGROUPS,
3277             ]);
3279         // Add the capability to the new assignment for student 1.
3280         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3281         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3282         role_assign($roleid, $student1->id, $assign->get_context()->id);
3283         accesslib_clear_all_caches_for_unit_testing();
3285         // Verify student1 has the ability to edit submissions for other users in their group, but not other groups.
3286         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3287         $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id));
3288         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3289         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3291         // Verify other students do not have the ability to edit submissions for other users.
3292         $this->assertTrue($assign->can_edit_submission($student2->id, $student2->id));
3293         $this->assertFalse($assign->can_edit_submission($student1->id, $student2->id));
3294         $this->assertFalse($assign->can_edit_submission($student3->id, $student2->id));
3295         $this->assertFalse($assign->can_edit_submission($student4->id, $student2->id));
3296     }
3298     /**
3299      * Test if the view blind details capability works
3300      */
3301     public function test_can_view_blind_details() {
3302         global $DB;
3304         $this->resetAfterTest();
3305         $course = $this->getDataGenerator()->create_course();
3306         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3307         $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
3308         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3310         $assign = $this->create_instance($course, [
3311                 'blindmarking' => 1,
3312             ]);
3314         $this->assertTrue($assign->is_blind_marking());