MDL-67673 phpunit: Remove deprecated assertContains() uses on strings
[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->assertStringContainsString(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->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
253         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS))), $output);
255         // Grant an extension.
256         $extendedtime = $time + (2 * DAYSECS);
257         $assign->testable_save_user_extension($student->id, $extendedtime);
258         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
259         $output = $assign->get_renderer()->render($gradingtable);
260         $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
261         $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
263         // Simulate a submission.
264         $this->setUser($student);
265         $submission = $assign->get_user_submission($student->id, true);
266         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
267         $assign->testable_update_submission($submission, $student->id, true, false);
268         $data = new stdClass();
269         $data->onlinetext_editor = [
270             'itemid' => file_get_unused_draft_itemid(),
271             'text' => 'Submission text',
272             'format' => FORMAT_MOODLE,
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->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
282         $this->assertStringContainsString(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->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
312         $difftime = time() - $time;
313         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
315         // Grant an extension that is in the past.
316         $assign->testable_save_user_extension($student->id, $time - (2 * DAYSECS));
317         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
318         $output = $assign->get_renderer()->render($gradingtable);
319         $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
320         $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
321         $difftime = time() - $time;
322         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
324         // Simulate a submission.
325         $this->setUser($student);
326         $submission = $assign->get_user_submission($student->id, true);
327         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
328         $assign->testable_update_submission($submission, $student->id, true, false);
329         $data = new stdClass();
330         $data->onlinetext_editor = [
331             'itemid' => file_get_unused_draft_itemid(),
332             'text' => 'Submission text',
333             'format' => FORMAT_MOODLE,
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->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
344         $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
346         $difftime = $submittedtime - $time;
347         $this->assertStringContainsString(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
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->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
374         $difftime = time() - $time;
375         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
377         // Simulate a student viewing the assignment without submitting.
378         $this->setUser($student);
379         $submission = $assign->get_user_submission($student->id, true);
380         $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
381         $assign->testable_update_submission($submission, $student->id, true, false);
382         $submittedtime = time();
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->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
391         $document = new DOMDocument();
392         @$document->loadHTML($output);
393         $xpath = new DOMXPath($document);
394         $this->assertEquals('', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])'));
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->assertStringContainsString('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->assertStringNotContainsString(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->assertStringNotContainsString(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->assertStringContainsString(get_string('submitassignment', 'assign'), $output, 'Can submit non empty onlinetext assignment');
854     }
856     /**
857      * Test new_submission_empty
858      *
859      * We only test combinations of plugins here. Individual plugins are tested
860      * in their respective test files.
861      *
862      * @dataProvider test_new_submission_empty_testcases
863      * @param string $data The file submission data
864      * @param bool $expected The expected return value
865      */
866     public function test_new_submission_empty($data, $expected) {
867         $this->resetAfterTest();
869         $course = $this->getDataGenerator()->create_course();
870         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
872         $assign = $this->create_instance($course, [
873                 'assignsubmission_file_enabled' => 1,
874                 'assignsubmission_file_maxfiles' => 12,
875                 'assignsubmission_file_maxsizebytes' => 10,
876                 'assignsubmission_onlinetext_enabled' => 1,
877             ]);
878         $this->setUser($student);
879         $submission = new stdClass();
881         if ($data['file'] && isset($data['file']['filename'])) {
882             $itemid = file_get_unused_draft_itemid();
883             $submission->files_filemanager = $itemid;
884             $data['file'] += ['contextid' => context_user::instance($student->id)->id, 'itemid' => $itemid];
885             $fs = get_file_storage();
886             $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']);
887         }
889         if ($data['onlinetext']) {
890             $submission->onlinetext_editor = ['text' => $data['onlinetext']];
891         }
893         $result = $assign->new_submission_empty($submission);
894         $this->assertTrue($result === $expected);
895     }
897     /**
898      * Dataprovider for the test_new_submission_empty testcase
899      *
900      * @return array of testcases
901      */
902     public function test_new_submission_empty_testcases() {
903         return [
904             'With file and onlinetext' => [
905                 [
906                     'file' => [
907                         'component' => 'user',
908                         'filearea' => 'draft',
909                         'filepath' => '/',
910                         'filename' => 'not_a_virus.exe'
911                     ],
912                     'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu'
913                 ],
914                 false
915             ]
916         ];
917     }
919     public function test_list_participants() {
920         global $CFG;
922         $this->resetAfterTest();
924         $course = $this->getDataGenerator()->create_course();
925         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
927         // Create 10 students.
928         for ($i = 0; $i < 10; $i++) {
929             $this->getDataGenerator()->create_and_enrol($course, 'student');
930         }
932         $this->setUser($teacher);
933         $assign = $this->create_instance($course, ['grade' => 100]);
935         $this->assertCount(10, $assign->list_participants(null, true));
936     }
938     public function test_list_participants_activeenrol() {
939         global $CFG, $DB;
941         $this->resetAfterTest();
943         $course = $this->getDataGenerator()->create_course();
944         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
946         // Create 10 students.
947         for ($i = 0; $i < 10; $i++) {
948             $this->getDataGenerator()->create_and_enrol($course, 'student');
949         }
951         // Create 10 suspended students.
952         for ($i = 0; $i < 10; $i++) {
953             $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
954         }
956         $this->setUser($teacher);
957         set_user_preference('grade_report_showonlyactiveenrol', false);
958         $assign = $this->create_instance($course, ['grade' => 100]);
960         $this->assertCount(10, $assign->list_participants(null, true));
961     }
963     public function test_list_participants_with_group_restriction() {
964         global $CFG;
966         $this->resetAfterTest();
968         $course = $this->getDataGenerator()->create_course();
969         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
970         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
971         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
972         $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
974         // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group.
975         $CFG->enableavailability = true;
977         $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
978         $assign = $this->create_instance($course, [
979                 'grade' => 100,
980                 'availability' => json_encode(
981                     \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)])
982                 ),
983             ]);
985         groups_add_member($specialgroup, $student);
986         groups_add_member($specialgroup, $otherstudent);
987         $this->assertEquals(2, count($assign->list_participants(null, true)));
988     }
990     public function test_get_participant_user_not_exist() {
991         $this->resetAfterTest();
992         $course = $this->getDataGenerator()->create_course();
994         $assign = $this->create_instance($course);
995         $this->assertNull($assign->get_participant('-1'));
996     }
998     public function test_get_participant_not_enrolled() {
999         $this->resetAfterTest();
1000         $course = $this->getDataGenerator()->create_course();
1001         $assign = $this->create_instance($course);
1003         $user = $this->getDataGenerator()->create_user();
1004         $this->assertNull($assign->get_participant($user->id));
1005     }
1007     public function test_get_participant_no_submission() {
1008         $this->resetAfterTest();
1009         $course = $this->getDataGenerator()->create_course();
1010         $assign = $this->create_instance($course);
1011         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1013         $participant = $assign->get_participant($student->id);
1015         $this->assertEquals($student->id, $participant->id);
1016         $this->assertFalse($participant->submitted);
1017         $this->assertFalse($participant->requiregrading);
1018         $this->assertFalse($participant->grantedextension);
1019     }
1021     public function test_get_participant_granted_extension() {
1022         $this->resetAfterTest();
1023         $course = $this->getDataGenerator()->create_course();
1024         $assign = $this->create_instance($course);
1025         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1026         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1028         $this->setUser($teacher);
1029         $assign->save_user_extension($student->id, time());
1030         $participant = $assign->get_participant($student->id);
1032         $this->assertEquals($student->id, $participant->id);
1033         $this->assertFalse($participant->submitted);
1034         $this->assertFalse($participant->requiregrading);
1035         $this->assertTrue($participant->grantedextension);
1036     }
1038     public function test_get_participant_with_ungraded_submission() {
1039         $this->resetAfterTest();
1040         $course = $this->getDataGenerator()->create_course();
1041         $assign = $this->create_instance($course);
1042         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1043         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1045         // Simulate a submission.
1046         $this->add_submission($student, $assign);
1047         $this->submit_for_grading($student, $assign);
1049         $participant = $assign->get_participant($student->id);
1051         $this->assertEquals($student->id, $participant->id);
1052         $this->assertTrue($participant->submitted);
1053         $this->assertTrue($participant->requiregrading);
1054         $this->assertFalse($participant->grantedextension);
1055     }
1057     public function test_get_participant_with_graded_submission() {
1058         $this->resetAfterTest();
1059         $course = $this->getDataGenerator()->create_course();
1060         $assign = $this->create_instance($course);
1061         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1062         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1064         // Simulate a submission.
1065         $this->add_submission($student, $assign);
1066         $this->submit_for_grading($student, $assign);
1068         $this->mark_submission($teacher, $assign, $student, 50.0);
1070         $data = new stdClass();
1071         $data->grade = '50.0';
1072         $assign->testable_apply_grade_to_user($data, $student->id, 0);
1074         $participant = $assign->get_participant($student->id);
1076         $this->assertEquals($student->id, $participant->id);
1077         $this->assertTrue($participant->submitted);
1078         $this->assertFalse($participant->requiregrading);
1079         $this->assertFalse($participant->grantedextension);
1080     }
1082     /**
1083      * No active group and non-group submissions disallowed => 2 groups.
1084      */
1085     public function test_count_teams_no_active_non_group_allowed() {
1086         $this->resetAfterTest();
1088         $course = $this->getDataGenerator()->create_course();
1089         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1090         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1091         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1093         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1094         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1095         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1096         groups_add_member($group1, $student1);
1097         groups_add_member($group2, $student2);
1099         $this->setUser($teacher);
1100         $assign = $this->create_instance($course, ['teamsubmission' => 1]);
1102         $this->assertEquals(2, $assign->count_teams());
1103     }
1105     /**
1106      * No active group and non group submissions allowed => 2 groups + the default one.
1107      */
1108     public function test_count_teams_non_group_allowed() {
1109         $this->resetAfterTest();
1111         $course = $this->getDataGenerator()->create_course();
1112         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1113         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1114         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1115         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1117         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1118         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1120         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1121         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1122         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1124         groups_add_member($group1, $student1);
1125         groups_add_member($group2, $student2);
1127         $assign = $this->create_instance($course, [
1128                 'teamsubmission' => 1,
1129                 'teamsubmissiongroupingid' => $grouping->id,
1130                 'preventsubmissionnotingroup' => false,
1131             ]);
1133         $this->setUser($teacher);
1134         $this->assertEquals(3, $assign->count_teams());
1136         // Active group only.
1137         $this->assertEquals(1, $assign->count_teams($group1->id));
1138         $this->assertEquals(1, $assign->count_teams($group2->id));
1139     }
1141     /**
1142      * Active group => just selected one.
1143      */
1144     public function test_count_teams_no_active_group() {
1145         $this->resetAfterTest();
1147         $course = $this->getDataGenerator()->create_course();
1148         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1149         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1150         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1151         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1153         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1154         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1156         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1157         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1158         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1160         groups_add_member($group1, $student1);
1161         groups_add_member($group2, $student2);
1163         $assign = $this->create_instance($course, [
1164                 'teamsubmission' => 1,
1165                 'preventsubmissionnotingroup' => true,
1166             ]);
1168         $this->setUser($teacher);
1169         $this->assertEquals(2, $assign->count_teams());
1171         // Active group only.
1172         $this->assertEquals(1, $assign->count_teams($group1->id));
1173         $this->assertEquals(1, $assign->count_teams($group2->id));
1174     }
1176     /**
1177      * Active group => just selected one.
1178      */
1179     public function test_count_teams_groups_only() {
1180         $this->resetAfterTest();
1182         $course = $this->getDataGenerator()->create_course();
1183         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1185         $assign = $this->create_instance($course, [
1186                 'teamsubmission' => 1,
1187                 'teamsubmissiongroupingid' => $grouping->id,
1188                 'preventsubmissionnotingroup' => false,
1189             ]);
1190         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1192         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1193         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1194         groups_add_member($group1, $student1);
1196         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1197         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1198         groups_add_member($group2, $student2);
1200         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1201         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1203         $this->setUser($teacher);
1205         $assign = $this->create_instance($course, [
1206                 'teamsubmission' => 1,
1207                 'preventsubmissionnotingroup' => true,
1208             ]);
1209         $this->assertEquals(2, $assign->count_teams());
1210     }
1212     public function test_submit_to_default_group() {
1213         global $DB, $SESSION;
1215         $this->resetAfterTest();
1217         $course = $this->getDataGenerator()->create_course();
1218         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1219         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1221         $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1222         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1224         $assign = $this->create_instance($course, [
1225                 'teamsubmission' => 1,
1226                 'assignsubmission_onlinetext_enabled' => 1,
1227                 'submissiondrafts' => 0,
1228                 'groupmode' => VISIBLEGROUPS,
1229             ]);
1231         $usergroup = $assign->get_submission_group($student->id);
1232         $this->assertFalse($usergroup, 'New student is in default group');
1234         // Add a submission.
1235         $this->add_submission($student, $assign);
1236         $this->submit_for_grading($student, $assign);
1238         // Set active groups to all groups.
1239         $this->setUser($teacher);
1240         $SESSION->activegroup[$course->id]['aag'][0] = 0;
1241         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1243         // Set an active group.
1244         $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id;
1245         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1246     }
1248     public function test_count_submissions_no_draft() {
1249         $this->resetAfterTest();
1251         $course = $this->getDataGenerator()->create_course();
1252         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1253         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1255         $assign = $this->create_instance($course, [
1256                 'assignsubmission_onlinetext_enabled' => 1,
1257             ]);
1259         $assign->get_user_submission($student->id, true);
1261         // Note: Drafts count as a submission.
1262         $this->assertEquals(0, $assign->count_grades());
1263         $this->assertEquals(0, $assign->count_submissions());
1264         $this->assertEquals(1, $assign->count_submissions(true));
1265         $this->assertEquals(0, $assign->count_submissions_need_grading());
1266         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1267         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1268         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1269         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1270     }
1272     public function test_count_submissions_draft() {
1273         $this->resetAfterTest();
1275         $course = $this->getDataGenerator()->create_course();
1276         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1277         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1279         $assign = $this->create_instance($course, [
1280                 'assignsubmission_onlinetext_enabled' => 1,
1281             ]);
1283         $this->add_submission($student, $assign);
1285         // Note: Drafts count as a submission.
1286         $this->assertEquals(0, $assign->count_grades());
1287         $this->assertEquals(1, $assign->count_submissions());
1288         $this->assertEquals(1, $assign->count_submissions(true));
1289         $this->assertEquals(0, $assign->count_submissions_need_grading());
1290         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1291         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1292         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1293         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1294     }
1296     public function test_count_submissions_submitted() {
1297         global $SESSION;
1299         $this->resetAfterTest();
1301         $course = $this->getDataGenerator()->create_course();
1302         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1303         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1305         $assign = $this->create_instance($course, [
1306                 'assignsubmission_onlinetext_enabled' => 1,
1307             ]);
1309         $this->add_submission($student, $assign);
1310         $this->submit_for_grading($student, $assign);
1312         $this->assertEquals(0, $assign->count_grades());
1313         $this->assertEquals(1, $assign->count_submissions());
1314         $this->assertEquals(1, $assign->count_submissions(true));
1315         $this->assertEquals(1, $assign->count_submissions_need_grading());
1316         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1317         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1318         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1319         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1320     }
1322     public function test_count_submissions_graded() {
1323         $this->resetAfterTest();
1325         $course = $this->getDataGenerator()->create_course();
1326         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1327         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1329         $assign = $this->create_instance($course, [
1330                 'assignsubmission_onlinetext_enabled' => 1,
1331             ]);
1333         $this->add_submission($student, $assign);
1334         $this->submit_for_grading($student, $assign);
1335         $this->mark_submission($teacher, $assign, $student, 50.0);
1337         // Although it has been graded, it is still marked as submitted.
1338         $this->assertEquals(1, $assign->count_grades());
1339         $this->assertEquals(1, $assign->count_submissions());
1340         $this->assertEquals(1, $assign->count_submissions(true));
1341         $this->assertEquals(0, $assign->count_submissions_need_grading());
1342         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1343         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1344         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1345         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1346     }
1348     public function test_count_submissions_graded_group() {
1349         global $SESSION;
1351         $this->resetAfterTest();
1353         $course = $this->getDataGenerator()->create_course();
1354         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1355         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1356         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1357         $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1358         groups_add_member($group, $student);
1360         $assign = $this->create_instance($course, [
1361                 'assignsubmission_onlinetext_enabled' => 1,
1362                 'groupmode' => VISIBLEGROUPS,
1363             ]);
1365         $this->add_submission($student, $assign);
1366         $this->submit_for_grading($student, $assign);
1368         // The user should still be listed when fetching all groups.
1369         $this->setUser($teacher);
1370         $SESSION->activegroup[$course->id]['aag'][0] = 0;
1371         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1373         // The user should still be listed when fetching just their group.
1374         $SESSION->activegroup[$course->id]['aag'][0] = $group->id;
1375         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1377         // The user should still be listed when fetching just their group.
1378         $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id;
1379         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1380     }
1382     // TODO
1383     public function x_test_count_submissions_for_team() {
1384         $this->resetAfterTest();
1386         $course = $this->getDataGenerator()->create_course();
1387         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1388         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1389         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1390         $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1391         groups_add_member($group, $student);
1393         $assign = $this->create_instance($course, [
1394                 'assignsubmission_onlinetext_enabled' => 1,
1395                 'teamsubmission' => 1,
1396             ]);
1398         // Add a graded submission.
1399         $this->add_submission($student, $assign);
1403         // Simulate adding a grade.
1404         $this->setUser($teacher);
1405         $data = new stdClass();
1406         $data->grade = '50.0';
1407         $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
1409         // Simulate a submission.
1410         $this->setUser($this->extrastudents[1]);
1411         $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
1412         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1413         $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1414         $data = new stdClass();
1415         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1416                                          'text' => 'Submission text',
1417                                          'format' => FORMAT_MOODLE);
1418         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1419         $plugin->save($submission, $data);
1421         // Simulate a submission.
1422         $this->setUser($this->extrastudents[2]);
1423         $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
1424         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1425         $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1426         $data = new stdClass();
1427         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1428                                          'text' => 'Submission text',
1429                                          'format' => FORMAT_MOODLE);
1430         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1431         $plugin->save($submission, $data);
1433         // Simulate a submission.
1434         $this->setUser($this->extrastudents[3]);
1435         $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
1436         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1437         $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1438         $data = new stdClass();
1439         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1440                                          'text' => 'Submission text',
1441                                          'format' => FORMAT_MOODLE);
1442         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1443         $plugin->save($submission, $data);
1445         // Simulate adding a grade.
1446         $this->setUser($teacher);
1447         $data = new stdClass();
1448         $data->grade = '50.0';
1449         $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1450         $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1452         // Create a new submission with status NEW.
1453         $this->setUser($this->extrastudents[4]);
1454         $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
1456         $this->assertEquals(2, $assign->count_grades());
1457         $this->assertEquals(4, $assign->count_submissions());
1458         $this->assertEquals(5, $assign->count_submissions(true));
1459         $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1460         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1461     }
1463     public function test_get_grading_userid_list_only_active() {
1464         $this->resetAfterTest();
1466         $course = $this->getDataGenerator()->create_course();
1467         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1468         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1469         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1471         $this->setUser($teacher);
1473         $assign = $this->create_instance($course);
1474         $this->assertCount(1, $assign->testable_get_grading_userid_list());
1475     }
1477     public function test_get_grading_userid_list_all() {
1478         $this->resetAfterTest();
1480         $course = $this->getDataGenerator()->create_course();
1481         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1482         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1483         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1485         $this->setUser($teacher);
1486         set_user_preference('grade_report_showonlyactiveenrol', false);
1488         $assign = $this->create_instance($course);
1489         $this->assertCount(2, $assign->testable_get_grading_userid_list());
1490     }
1492     public function test_cron() {
1493         global $PAGE;
1494         $this->resetAfterTest();
1496         // First run cron so there are no messages waiting to be sent (from other tests).
1497         cron_setup_user();
1498         assign::cron();
1500         $course = $this->getDataGenerator()->create_course();
1501         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1502         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1504         // Now create an assignment and add some feedback.
1505         $this->setUser($teacher);
1506         $assign = $this->create_instance($course, [
1507                 'sendstudentnotifications' => 1,
1508             ]);
1510         $this->add_submission($student, $assign);
1511         $this->submit_for_grading($student, $assign);
1512         $this->mark_submission($teacher, $assign, $student, 50.0);
1514         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1515         cron_setup_user();
1516         $sink = $this->redirectMessages();
1517         assign::cron();
1518         $messages = $sink->get_messages();
1520         $this->assertEquals(1, count($messages));
1521         $this->assertEquals(1, $messages[0]->notification);
1522         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1523         // Test customdata.
1524         $customdata = json_decode($messages[0]->customdata);
1525         $this->assertEquals($assign->get_course_module()->id, $customdata->cmid);
1526         $this->assertEquals($assign->get_instance()->id, $customdata->instance);
1527         $this->assertEquals('feedbackavailable', $customdata->messagetype);
1528         $userpicture = new user_picture($teacher);
1529         $userpicture->size = 1; // Use f1 size.
1530         $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
1531         $this->assertEquals(0, $customdata->uniqueidforuser);   // Not used in this case.
1532         $this->assertFalse($customdata->blindmarking);
1533     }
1535     public function test_cron_without_notifications() {
1536         $this->resetAfterTest();
1538         // First run cron so there are no messages waiting to be sent (from other tests).
1539         cron_setup_user();
1540         assign::cron();
1542         $course = $this->getDataGenerator()->create_course();
1543         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1544         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1546         // Now create an assignment and add some feedback.
1547         $this->setUser($teacher);
1548         $assign = $this->create_instance($course, [
1549                 'sendstudentnotifications' => 1,
1550             ]);
1552         $this->add_submission($student, $assign);
1553         $this->submit_for_grading($student, $assign);
1554         $this->mark_submission($teacher, $assign, $student, 50.0, [
1555                 'sendstudentnotifications' => 0,
1556             ]);
1558         cron_setup_user();
1559         $sink = $this->redirectMessages();
1560         assign::cron();
1561         $messages = $sink->get_messages();
1563         $this->assertEquals(0, count($messages));
1564     }
1566     public function test_cron_regraded() {
1567         $this->resetAfterTest();
1569         // First run cron so there are no messages waiting to be sent (from other tests).
1570         cron_setup_user();
1571         assign::cron();
1573         $course = $this->getDataGenerator()->create_course();
1574         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1575         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1577         // Now create an assignment and add some feedback.
1578         $this->setUser($teacher);
1579         $assign = $this->create_instance($course, [
1580                 'sendstudentnotifications' => 1,
1581             ]);
1583         $this->add_submission($student, $assign);
1584         $this->submit_for_grading($student, $assign);
1585         $this->mark_submission($teacher, $assign, $student, 50.0);
1587         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1588         cron_setup_user();
1589         assign::cron();
1591         // Regrade.
1592         $this->mark_submission($teacher, $assign, $student, 50.0);
1594         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1595         cron_setup_user();
1596         $sink = $this->redirectMessages();
1597         assign::cron();
1598         $messages = $sink->get_messages();
1600         $this->assertEquals(1, count($messages));
1601         $this->assertEquals(1, $messages[0]->notification);
1602         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1603     }
1605     /**
1606      * Test delivery of grade notifications as controlled by marking workflow.
1607      */
1608     public function test_markingworkflow_cron() {
1609         $this->resetAfterTest();
1611         // First run cron so there are no messages waiting to be sent (from other tests).
1612         cron_setup_user();
1613         assign::cron();
1615         $course = $this->getDataGenerator()->create_course();
1616         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1617         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1619         // Now create an assignment and add some feedback.
1620         $this->setUser($teacher);
1621         $assign = $this->create_instance($course, [
1622                 'sendstudentnotifications' => 1,
1623                 'markingworkflow' => 1,
1624             ]);
1626         // Mark a submission but set the workflowstate to an unreleased state.
1627         // This should not trigger a notification.
1628         $this->add_submission($student, $assign);
1629         $this->submit_for_grading($student, $assign);
1630         $this->mark_submission($teacher, $assign, $student, 50.0, [
1631                 'sendstudentnotifications' => 1,
1632                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1633             ]);
1635         cron_setup_user();
1636         $sink = $this->redirectMessages();
1637         assign::cron();
1638         $messages = $sink->get_messages();
1640         $this->assertEquals(0, count($messages));
1642         // Transition to the released state.
1643         $this->setUser($teacher);
1644         $submission = $assign->get_user_submission($student->id, true);
1645         $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1646         $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1648         // Now run cron and see that one message was sent.
1649         cron_setup_user();
1650         $sink = $this->redirectMessages();
1651         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1652         assign::cron();
1653         $messages = $sink->get_messages();
1655         $this->assertEquals(1, count($messages));
1656         $this->assertEquals(1, $messages[0]->notification);
1657         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1658     }
1660     public function test_cron_message_includes_courseid() {
1661         $this->resetAfterTest();
1663         // First run cron so there are no messages waiting to be sent (from other tests).
1664         cron_setup_user();
1665         assign::cron();
1667         $course = $this->getDataGenerator()->create_course();
1668         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1669         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1671         // Now create an assignment and add some feedback.
1672         $this->setUser($teacher);
1673         $assign = $this->create_instance($course, [
1674                 'sendstudentnotifications' => 1,
1675             ]);
1677         // Mark a submission but set the workflowstate to an unreleased state.
1678         // This should not trigger a notification.
1679         $this->add_submission($student, $assign);
1680         $this->submit_for_grading($student, $assign);
1681         $this->mark_submission($teacher, $assign, $student);
1682         phpunit_util::stop_message_redirection();
1684         // Now run cron and see that one message was sent.
1685         cron_setup_user();
1686         $this->preventResetByRollback();
1687         $sink = $this->redirectEvents();
1688         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1689         assign::cron();
1691         $events = $sink->get_events();
1692         $event = reset($events);
1693         $this->assertInstanceOf('\core\event\notification_sent', $event);
1694         $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1695         $sink->close();
1696     }
1698     public function test_is_graded() {
1699         $this->resetAfterTest();
1701         $course = $this->getDataGenerator()->create_course();
1702         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1703         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1704         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1706         $assign = $this->create_instance($course);
1708         $this->add_submission($student, $assign);
1709         $this->submit_for_grading($student, $assign);
1710         $this->mark_submission($teacher, $assign, $student, 50.0);
1712         $this->setUser($teacher);
1713         $this->assertEquals(true, $assign->testable_is_graded($student->id));
1714         $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1715     }
1717     public function test_can_grade() {
1718         global $DB;
1720         $this->resetAfterTest();
1722         $course = $this->getDataGenerator()->create_course();
1723         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1724         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1726         $assign = $this->create_instance($course);
1728         $this->setUser($student);
1729         $this->assertEquals(false, $assign->can_grade());
1731         $this->setUser($teacher);
1732         $this->assertEquals(true, $assign->can_grade());
1734         // Test the viewgrades capability for other users.
1735         $this->setUser();
1736         $this->assertTrue($assign->can_grade($teacher->id));
1737         $this->assertFalse($assign->can_grade($student->id));
1739         // Test the viewgrades capability - without mod/assign:grade.
1740         $this->setUser($student);
1742         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1743         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1744         $this->assertEquals(false, $assign->can_grade());
1745     }
1747     public function test_can_view_submission() {
1748         global $DB;
1750         $this->resetAfterTest();
1752         $course = $this->getDataGenerator()->create_course();
1753         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1754         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1755         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1756         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1757         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1759         $assign = $this->create_instance($course);
1761         $this->setUser($student);
1762         $this->assertEquals(true, $assign->can_view_submission($student->id));
1763         $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1764         $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1766         $this->setUser($teacher);
1767         $this->assertEquals(true, $assign->can_view_submission($student->id));
1768         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1769         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1770         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1772         $this->setUser($editingteacher);
1773         $this->assertEquals(true, $assign->can_view_submission($student->id));
1774         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1775         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1776         $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1778         // Test the viewgrades capability - without mod/assign:grade.
1779         $this->setUser($student);
1780         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1781         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1782         $this->assertEquals(true, $assign->can_view_submission($student->id));
1783         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1784         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1785         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1786     }
1788     public function test_update_submission() {
1789         $this->resetAfterTest();
1791         $course = $this->getDataGenerator()->create_course();
1792         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1793         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1795         $assign = $this->create_instance($course);
1797         $this->add_submission($student, $assign);
1798         $submission = $assign->get_user_submission($student->id, 0);
1799         $assign->testable_update_submission($submission, $student->id, true, true);
1801         $this->setUser($teacher);
1803         // Verify the gradebook update.
1804         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1806         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1807         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1808     }
1810     public function test_update_submission_team() {
1811         $this->resetAfterTest();
1813         $course = $this->getDataGenerator()->create_course();
1814         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1815         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1817         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1818         groups_add_member($group, $student);
1820         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1821         groups_add_member($group, $otherstudent);
1823         $assign = $this->create_instance($course, [
1824                 'teamsubmission' => 1,
1825             ]);
1827         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1828         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1829         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1831         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1832         $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1833         $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1835         $this->add_submission($student, $assign);
1836         $submission = $assign->get_group_submission($student->id, 0, true);
1837         $assign->testable_update_submission($submission, $student->id, true, true);
1839         // Verify the gradebook update for the student.
1840         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1842         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1843         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1845         // Verify the gradebook update for the other student.
1846         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1848         $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1849         $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1850     }
1852     public function test_update_submission_suspended() {
1853         $this->resetAfterTest();
1855         $course = $this->getDataGenerator()->create_course();
1856         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1857         $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1859         $assign = $this->create_instance($course);
1861         $this->add_submission($student, $assign);
1862         $submission = $assign->get_user_submission($student->id, 0);
1863         $assign->testable_update_submission($submission, $student->id, true, false);
1865         $this->setUser($teacher);
1867         // Verify the gradebook update.
1868         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1870         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1871         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1872     }
1874     public function test_update_submission_blind() {
1875         $this->resetAfterTest();
1877         $course = $this->getDataGenerator()->create_course();
1878         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1879         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1881         $assign = $this->create_instance($course, [
1882                 'blindmarking' => 1,
1883             ]);
1885         $this->add_submission($student, $assign);
1886         $submission = $assign->get_user_submission($student->id, 0);
1887         $assign->testable_update_submission($submission, $student->id, true, false);
1889         // Verify the gradebook update.
1890         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1892         // The usermodified is not set because this is blind marked.
1893         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1894         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1895     }
1897     public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
1898         global $PAGE;
1900         $this->resetAfterTest();
1902         $course = $this->getDataGenerator()->create_course();
1903         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1904         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1906         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1907         groups_add_member($group, $student);
1909         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1910         groups_add_member($group, $otherstudent);
1912         $assign = $this->create_instance($course, [
1913                 'teamsubmission' => 1,
1914                 'assignsubmission_onlinetext_enabled' => 1,
1915                 'submissiondrafts' => 1,
1916                 'requireallteammemberssubmit' => 1,
1917             ]);
1919         // Now verify group assignments.
1920         $this->setUser($teacher);
1921         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1923         // Add a submission.
1924         $this->add_submission($student, $assign);
1926         // Check we can see the submit button.
1927         $this->setUser($student);
1928         $output = $assign->view_student_summary($student, true);
1929         $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1931         $submission = $assign->get_group_submission($student->id, 0, true);
1932         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1933         $assign->testable_update_submission($submission, $student->id, true, true);
1935         // Check that the student does not see "Submit" button.
1936         $output = $assign->view_student_summary($student, true);
1937         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1939         // Change to another user in the same group.
1940         $this->setUser($otherstudent);
1941         $output = $assign->view_student_summary($otherstudent, true);
1942         $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1944         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
1945         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1946         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
1947         $output = $assign->view_student_summary($otherstudent, true);
1948         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1949     }
1951     public function test_group_submissions_submit_for_marking() {
1952         global $PAGE;
1954         $this->resetAfterTest();
1956         $course = $this->getDataGenerator()->create_course();
1957         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1958         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1960         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1961         groups_add_member($group, $student);
1963         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1964         groups_add_member($group, $otherstudent);
1966         // Now verify group assignments.
1967         $this->setUser($teacher);
1968         $time = time();
1969         $assign = $this->create_instance($course, [
1970                 'teamsubmission' => 1,
1971                 'assignsubmission_onlinetext_enabled' => 1,
1972                 'submissiondrafts' => 1,
1973                 'requireallteammemberssubmit' => 0,
1974                 'duedate' => $time - (2 * DAYSECS),
1975             ]);
1976         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1978         // Add a submission.
1979         $this->add_submission($student, $assign);
1982         // Check we can see the submit button.
1983         $output = $assign->view_student_summary($student, true);
1984         $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1985         $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
1986         $difftime = time() - $time;
1987         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
1989         $submission = $assign->get_group_submission($student->id, 0, true);
1990         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1991         $assign->testable_update_submission($submission, $student->id, true, true);
1993         // Check that the student does not see "Submit" button.
1994         $output = $assign->view_student_summary($student, true);
1995         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1997         // Change to another user in the same group.
1998         $this->setUser($otherstudent);
1999         $output = $assign->view_student_summary($otherstudent, true);
2000         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2002         // Check that time remaining is not overdue.
2003         $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2004         $difftime = time() - $time;
2005         $this->assertStringContainsString(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2007         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2008         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2009         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2010         $output = $assign->view_student_summary($otherstudent, true);
2011         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2012     }
2014     public function test_submissions_open() {
2015         global $DB;
2017         $this->resetAfterTest();
2019         $course = $this->getDataGenerator()->create_course();
2020         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2021         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2022         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2023         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2024         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
2026         $this->setAdminUser();
2028         $now = time();
2029         $tomorrow = $now + DAYSECS;
2030         $oneweek = $now + WEEKSECS;
2031         $yesterday = $now - DAYSECS;
2033         $assign = $this->create_instance($course);
2034         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2036         $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
2037         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2039         $assign = $this->create_instance($course, ['duedate' => $yesterday]);
2040         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2042         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
2043         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2045         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2046         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2048         $assign->testable_save_user_extension($student->id, $tomorrow);
2049         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2051         $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2052         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2054         $this->setUser($student);
2055         $submission = $assign->get_user_submission($student->id, true);
2056         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2057         $assign->testable_update_submission($submission, $student->id, true, false);
2059         $this->setUser($teacher);
2060         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2061     }
2063     public function test_get_graders() {
2064         global $DB;
2066         $this->resetAfterTest();
2068         $course = $this->getDataGenerator()->create_course();
2069         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2070         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2071         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2073         $this->setAdminUser();
2075         // Create an assignment with no groups.
2076         $assign = $this->create_instance($course);
2077         $this->assertCount(2, $assign->testable_get_graders($student->id));
2078     }
2080     public function test_get_graders_separate_groups() {
2081         global $DB;
2083         $this->resetAfterTest();
2085         $course = $this->getDataGenerator()->create_course();
2086         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2087         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2088         $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2089         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2090         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2091         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2093         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2094         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2095         groups_add_member($group1, $student);
2097         $this->setAdminUser();
2099         // Force create an assignment with SEPARATEGROUPS.
2100         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2101         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2103         $assign = $this->create_instance($course, [
2104                 'groupingid' => $grouping->id,
2105                 'groupmode' => SEPARATEGROUPS,
2106             ]);
2108         $this->assertCount(4, $assign->testable_get_graders($student->id));
2110         // Note the second student is in a group that is not in the grouping.
2111         // This means that we get all graders that are not in a group in the grouping.
2112         $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2113     }
2115     public function test_get_notified_users() {
2116         global $CFG, $DB;
2118         $this->resetAfterTest();
2120         $course = $this->getDataGenerator()->create_course();
2121         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2122         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2123         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2125         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2126         groups_add_member($group1, $teacher);
2128         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2129         groups_add_member($group1, $editingteacher);
2131         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2132         groups_add_member($group1, $student);
2134         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2135         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2137         $capability = 'mod/assign:receivegradernotifications';
2138         $coursecontext = context_course::instance($course->id);
2139         $role = $DB->get_record('role', array('shortname' => 'teacher'));
2141         $this->setUser($teacher);
2143         // Create an assignment with no groups.
2144         $assign = $this->create_instance($course);
2146         $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2148         // Change nonediting teachers role to not receive grader notifications.
2149         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2151         // Only the editing teachers will be returned.
2152         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2154         // Note the second student is in a group that is not in the grouping.
2155         // This means that we get all graders that are not in a group in the grouping.
2156         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2157     }
2159     public function test_get_notified_users_in_grouping() {
2160         global $CFG, $DB;
2162         $this->resetAfterTest();
2164         $course = $this->getDataGenerator()->create_course();
2165         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2166         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2167         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2169         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2170         groups_add_member($group1, $teacher);
2172         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2173         groups_add_member($group1, $editingteacher);
2175         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2176         groups_add_member($group1, $student);
2178         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2179         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2181         // Force create an assignment with SEPARATEGROUPS.
2182         $assign = $this->create_instance($course, [
2183                 'groupingid' => $grouping->id,
2184                 'groupmode' => SEPARATEGROUPS,
2185             ]);
2187         // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2188         $this->setUser($student);
2189         $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2191         // Note the second student is in a group that is not in the grouping.
2192         // This means that we get all graders that are not in a group in the grouping.
2193         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2195         // Change nonediting teachers role to not receive grader notifications.
2196         $capability = 'mod/assign:receivegradernotifications';
2197         $coursecontext = context_course::instance($course->id);
2198         $role = $DB->get_record('role', ['shortname' => 'teacher']);
2199         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2201         // Only the editing teachers will be returned.
2202         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2204         // Note the second student is in a group that is not in the grouping.
2205         // This means that we get all graders that are not in a group in the grouping.
2206         // Unfortunately there are no editing teachers who are not in a group.
2207         $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2208     }
2210     public function test_group_members_only() {
2211         global $CFG;
2213         $this->resetAfterTest();
2215         $course = $this->getDataGenerator()->create_course();
2216         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2217         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2218         $this->getDataGenerator()->create_grouping_group([
2219                 'groupid' => $group1->id,
2220                 'groupingid' => $grouping->id,
2221             ]);
2223         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2224         $this->getDataGenerator()->create_grouping_group([
2225                 'groupid' => $group2->id,
2226                 'groupingid' => $grouping->id,
2227             ]);
2229         $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2231         // Add users in the following groups
2232         // - Teacher - Group 1.
2233         // - Student - Group 1.
2234         // - Student - Group 2.
2235         // - Student - Unrelated Group
2236         // - Student - No group.
2237         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2238         groups_add_member($group1, $teacher);
2240         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2241         groups_add_member($group1, $student);
2243         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2244         groups_add_member($group2, $otherstudent);
2246         $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2247         groups_add_member($group2, $otherstudent);
2249         $this->getDataGenerator()->create_and_enrol($course, 'student');
2251         $this->setAdminUser();
2253         $CFG->enableavailability = true;
2254         $assign = $this->create_instance($course, [], [
2255                 'availability' => json_encode(
2256                     \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2257                 ),
2258                 'groupingid' => $grouping->id,
2259             ]);
2261         // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2262         // group, or the student in an unrelated group.
2263         $this->setUser($teacher);
2264         $participants = $assign->list_participants(0, true);
2265         $this->assertCount(2, $participants);
2266         $this->assertTrue(isset($participants[$student->id]));
2267         $this->assertTrue(isset($participants[$otherstudent->id]));
2268     }
2270     public function test_get_uniqueid_for_user() {
2271         $this->resetAfterTest();
2273         $course = $this->getDataGenerator()->create_course();
2274         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2275         $students = [];
2276         for ($i = 0; $i < 10; $i++) {
2277             $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2278             $students[$student->id] = $student;
2279         }
2281         $this->setUser($teacher);
2282         $assign = $this->create_instance($course);
2284         foreach ($students as $student) {
2285             $uniqueid = $assign->get_uniqueid_for_user($student->id);
2286             $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2287         }
2288     }
2290     public function test_show_student_summary() {
2291         global $CFG, $PAGE;
2293         $this->resetAfterTest();
2295         $course = $this->getDataGenerator()->create_course();
2296         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2297         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2298         $this->setUser($teacher);
2299         $assign = $this->create_instance($course);
2300         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2302         // No feedback should be available because this student has not been graded.
2303         $this->setUser($student);
2304         $output = $assign->view_student_summary($student, true);
2305         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if there is no grade');
2307         // Simulate adding a grade.
2308         $this->add_submission($student, $assign);
2309         $this->submit_for_grading($student, $assign);
2310         $this->mark_submission($teacher, $assign, $student);
2312         // Now we should see the feedback.
2313         $this->setUser($student);
2314         $output = $assign->view_student_summary($student, true);
2315         $this->assertRegexp('/Feedback/', $output, 'Show feedback if there is a grade');
2317         // Now hide the grade in gradebook.
2318         $this->setUser($teacher);
2319         require_once($CFG->libdir.'/gradelib.php');
2320         $gradeitem = new grade_item(array(
2321             'itemtype'      => 'mod',
2322             'itemmodule'    => 'assign',
2323             'iteminstance'  => $assign->get_instance()->id,
2324             'courseid'      => $course->id));
2326         $gradeitem->set_hidden(1, false);
2328         // No feedback should be available because the grade is hidden.
2329         $this->setUser($student);
2330         $output = $assign->view_student_summary($student, true);
2331         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2332     }
2334     public function test_show_student_summary_with_feedback() {
2335         global $CFG, $PAGE;
2337         $this->resetAfterTest();
2339         $course = $this->getDataGenerator()->create_course();
2340         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2341         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2342         $this->setUser($teacher);
2343         $assign = $this->create_instance($course, [
2344                 'assignfeedback_comments_enabled' => 1
2345             ]);
2346         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2348         // No feedback should be available because this student has not been graded.
2349         $this->setUser($student);
2350         $output = $assign->view_student_summary($student, true);
2351         $this->assertNotRegexp('/Feedback/', $output);
2353         // Simulate adding a grade.
2354         $this->add_submission($student, $assign);
2355         $this->submit_for_grading($student, $assign);
2356         $this->mark_submission($teacher, $assign, $student, null, [
2357                 'assignfeedbackcomments_editor' => [
2358                     'text' => 'Tomato sauce',
2359                     'format' => FORMAT_MOODLE,
2360                 ],
2361             ]);
2363         // Should have feedback but no grade.
2364         $this->setUser($student);
2365         $output = $assign->view_student_summary($student, true);
2366         $this->assertRegexp('/Feedback/', $output);
2367         $this->assertRegexp('/Tomato sauce/', $output);
2368         $this->assertNotRegexp('/Grade/', $output, 'Do not show grade when there is no grade.');
2369         $this->assertNotRegexp('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2371         // Add a grade now.
2372         $this->mark_submission($teacher, $assign, $student, 50.0, [
2373                 'assignfeedbackcomments_editor' => [
2374                     'text' => 'Bechamel sauce',
2375                     'format' => FORMAT_MOODLE,
2376                 ],
2377             ]);
2379         // Should have feedback but no grade.
2380         $this->setUser($student);
2381         $output = $assign->view_student_summary($student, true);
2382         $this->assertNotRegexp('/Tomato sauce/', $output);
2383         $this->assertRegexp('/Bechamel sauce/', $output);
2384         $this->assertRegexp('/Grade/', $output);
2385         $this->assertRegexp('/Graded on/', $output);
2387         // Now hide the grade in gradebook.
2388         $this->setUser($teacher);
2389         $gradeitem = new grade_item(array(
2390             'itemtype'      => 'mod',
2391             'itemmodule'    => 'assign',
2392             'iteminstance'  => $assign->get_instance()->id,
2393             'courseid'      => $course->id));
2395         $gradeitem->set_hidden(1, false);
2397         // No feedback should be available because the grade is hidden.
2398         $this->setUser($student);
2399         $output = $assign->view_student_summary($student, true);
2400         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2401     }
2403     /**
2404      * Test reopen behavior when in "Manual" mode.
2405      */
2406     public function test_attempt_reopen_method_manual() {
2407         global $PAGE;
2409         $this->resetAfterTest();
2410         $course = $this->getDataGenerator()->create_course();
2411         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2412         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2414         $assign = $this->create_instance($course, [
2415                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2416                 'maxattempts' => 3,
2417                 'submissiondrafts' => 1,
2418                 'assignsubmission_onlinetext_enabled' => 1,
2419             ]);
2420         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2422         // Student should be able to see an add submission button.
2423         $this->setUser($student);
2424         $output = $assign->view_student_summary($student, true);
2425         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2427         // Add a submission.
2428         $this->add_submission($student, $assign);
2429         $this->submit_for_grading($student, $assign);
2431         // Verify the student cannot make changes to the submission.
2432         $output = $assign->view_student_summary($student, true);
2433         $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2435         // Mark the submission.
2436         $this->mark_submission($teacher, $assign, $student);
2438         // Check the student can see the grade.
2439         $this->setUser($student);
2440         $output = $assign->view_student_summary($student, true);
2441         $this->assertNotEquals(false, strpos($output, '50.0'));
2443         // Allow the student another attempt.
2444         $teacher->ignoresesskey = true;
2445         $this->setUser($teacher);
2446         $result = $assign->testable_process_add_attempt($student->id);
2447         $this->assertEquals(true, $result);
2449         // Check that the previous attempt is now in the submission history table.
2450         $this->setUser($student);
2451         $output = $assign->view_student_summary($student, true);
2452         // Need a better check.
2453         $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2455         // Check that the student now has a button for Add a new attempt".
2456         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2457         // Check that the student now does not have a button for Submit.
2458         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2460         // Check that the student now has a submission history.
2461         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2463         $this->setUser($teacher);
2464         // Check that the grading table loads correctly and contains this user.
2465         // This is also testing that we do not get duplicate rows in the grading table.
2466         $gradingtable = new assign_grading_table($assign, 100, '', 0, true);
2467         $output = $assign->get_renderer()->render($gradingtable);
2468         $this->assertEquals(true, strpos($output, $student->lastname));
2470         // Should be 1 not 2.
2471         $this->assertEquals(1, $assign->count_submissions());
2472         $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2473         $this->assertEquals(0, $assign->count_submissions_need_grading());
2474         $this->assertEquals(1, $assign->count_grades());
2476         // Change max attempts to unlimited.
2477         $formdata = clone($assign->get_instance());
2478         $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2479         $formdata->instance = $formdata->id;
2480         $assign->update_instance($formdata);
2482         // Mark the submission again.
2483         $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2485         // Check the grade exists.
2486         $this->setUser($teacher);
2487         $grades = $assign->get_user_grades_for_gradebook($student->id);
2488         $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2490         // Check we can reopen still.
2491         $result = $assign->testable_process_add_attempt($student->id);
2492         $this->assertEquals(true, $result);
2494         // Should no longer have a grade because there is no grade for the latest attempt.
2495         $grades = $assign->get_user_grades_for_gradebook($student->id);
2496         $this->assertEmpty($grades);
2497     }
2499     /**
2500      * Test reopen behavior when in "Reopen until pass" mode.
2501      */
2502     public function test_attempt_reopen_method_untilpass() {
2503         global $PAGE;
2505         $this->resetAfterTest();
2506         $course = $this->getDataGenerator()->create_course();
2507         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2508         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2510         $assign = $this->create_instance($course, [
2511                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2512                 'maxattempts' => 3,
2513                 'submissiondrafts' => 1,
2514                 'assignsubmission_onlinetext_enabled' => 1,
2515             ]);
2516         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2518         // Set grade to pass to 80.
2519         $gradeitem = $assign->get_grade_item();
2520         $gradeitem->gradepass = '80.0';
2521         $gradeitem->update();
2523         // Student should be able to see an add submission button.
2524         $this->setUser($student);
2525         $output = $assign->view_student_summary($student, true);
2526         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2528         // Add a submission.
2529         $this->add_submission($student, $assign);
2530         $this->submit_for_grading($student, $assign);
2532         // Verify the student cannot make a new attempt.
2533         $output = $assign->view_student_summary($student, true);
2534         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2536         // Mark the submission as non-passing.
2537         $this->mark_submission($teacher, $assign, $student, 50.0);
2539         // Check the student can see the grade.
2540         $this->setUser($student);
2541         $output = $assign->view_student_summary($student, true);
2542         $this->assertNotEquals(false, strpos($output, '50.0'));
2544         // Check that the student now has a button for Add a new attempt.
2545         $output = $assign->view_student_summary($student, true);
2546         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2548         // Check that the student now does not have a button for Submit.
2549         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2551         // Check that the student now has a submission history.
2552         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2554         // Add a second submission.
2555         $this->add_submission($student, $assign);
2556         $this->submit_for_grading($student, $assign);
2558         // Mark the submission as passing.
2559         $this->mark_submission($teacher, $assign, $student, 80.0);
2561         // Check that the student does not have a button for Add a new attempt.
2562         $this->setUser($student);
2563         $output = $assign->view_student_summary($student, true);
2564         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2566         // Re-mark the submission as not passing.
2567         $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2569         // Check that the student now has a button for Add a new attempt.
2570         $this->setUser($student);
2571         $output = $assign->view_student_summary($student, true);
2572         $this->assertRegexp('/' . get_string('addnewattempt', 'assign') . '/', $output);
2573         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2574     }
2576     public function test_attempt_reopen_method_untilpass_passing() {
2577         global $PAGE;
2579         $this->resetAfterTest();
2580         $course = $this->getDataGenerator()->create_course();
2581         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2582         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2584         $assign = $this->create_instance($course, [
2585                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2586                 'maxattempts' => 3,
2587                 'submissiondrafts' => 1,
2588                 'assignsubmission_onlinetext_enabled' => 1,
2589             ]);
2590         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2592         // Set grade to pass to 80.
2593         $gradeitem = $assign->get_grade_item();
2594         $gradeitem->gradepass = '80.0';
2595         $gradeitem->update();
2597         // Student should be able to see an add submission button.
2598         $this->setUser($student);
2599         $output = $assign->view_student_summary($student, true);
2600         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2602         // Add a submission as a student.
2603         $this->add_submission($student, $assign);
2604         $this->submit_for_grading($student, $assign);
2606         // Mark the submission as passing.
2607         $this->mark_submission($teacher, $assign, $student, 100.0);
2609         // Check the student can see the grade.
2610         $this->setUser($student);
2611         $output = $assign->view_student_summary($student, true);
2612         $this->assertNotEquals(false, strpos($output, '100.0'));
2614         // Check that the student does not have a button for Add a new attempt.
2615         $output = $assign->view_student_summary($student, true);
2616         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2617     }
2619     public function test_attempt_reopen_method_untilpass_no_passing_requirement() {
2620         global $PAGE;
2622         $this->resetAfterTest();
2623         $course = $this->getDataGenerator()->create_course();
2624         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2625         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2627         $assign = $this->create_instance($course, [
2628                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2629                 'maxattempts' => 3,
2630                 'submissiondrafts' => 1,
2631                 'assignsubmission_onlinetext_enabled' => 1,
2632             ]);
2633         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2635         // Set grade to pass to 0, so that no attempts should reopen.
2636         $gradeitem = $assign->get_grade_item();
2637         $gradeitem->gradepass = '0';
2638         $gradeitem->update();
2640         // Student should be able to see an add submission button.
2641         $this->setUser($student);
2642         $output = $assign->view_student_summary($student, true);
2643         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2645         // Add a submission.
2646         $this->add_submission($student, $assign);
2647         $this->submit_for_grading($student, $assign);
2649         // Mark the submission with any grade.
2650         $this->mark_submission($teacher, $assign, $student, 0.0);
2652         // Check the student can see the grade.
2653         $this->setUser($student);
2654         $output = $assign->view_student_summary($student, true);
2655         $this->assertNotEquals(false, strpos($output, '0.0'));
2657         // Check that the student does not have a button for Add a new attempt.
2658         $output = $assign->view_student_summary($student, true);
2659         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2660     }
2662     /**
2663      * Test student visibility for each stage of the marking workflow.
2664      */
2665     public function test_markingworkflow() {
2666         global $PAGE;
2668         $this->resetAfterTest();
2669         $course = $this->getDataGenerator()->create_course();
2670         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2671         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2673         $assign = $this->create_instance($course, [
2674                 'markingworkflow' => 1,
2675             ]);
2677         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2679         // Mark the submission and set to notmarked.
2680         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2681                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2682             ]);
2684         // Check the student can't see the grade.
2685         $this->setUser($student);
2686         $output = $assign->view_student_summary($student, true);
2687         $this->assertEquals(false, strpos($output, '50.0'));
2689         // Make sure the grade isn't pushed to the gradebook.
2690         $grades = $assign->get_user_grades_for_gradebook($student->id);
2691         $this->assertEmpty($grades);
2693         // Mark the submission and set to inmarking.
2694         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2695                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2696             ]);
2698         // Check the student can't see the grade.
2699         $this->setUser($student);
2700         $output = $assign->view_student_summary($student, true);
2701         $this->assertEquals(false, strpos($output, '50.0'));
2703         // Make sure the grade isn't pushed to the gradebook.
2704         $grades = $assign->get_user_grades_for_gradebook($student->id);
2705         $this->assertEmpty($grades);
2707         // Mark the submission and set to readyforreview.
2708         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2709                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2710             ]);
2712         // Check the student can't see the grade.
2713         $this->setUser($student);
2714         $output = $assign->view_student_summary($student, true);
2715         $this->assertEquals(false, strpos($output, '50.0'));
2717         // Make sure the grade isn't pushed to the gradebook.
2718         $grades = $assign->get_user_grades_for_gradebook($student->id);
2719         $this->assertEmpty($grades);
2721         // Mark the submission and set to inreview.
2722         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2723                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2724             ]);
2726         // Check the student can't see the grade.
2727         $this->setUser($student);
2728         $output = $assign->view_student_summary($student, true);
2729         $this->assertEquals(false, strpos($output, '50.0'));
2731         // Make sure the grade isn't pushed to the gradebook.
2732         $grades = $assign->get_user_grades_for_gradebook($student->id);
2733         $this->assertEmpty($grades);
2735         // Mark the submission and set to readyforrelease.
2736         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2737                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
2738             ]);
2740         // Check the student can't see the grade.
2741         $this->setUser($student);
2742         $output = $assign->view_student_summary($student, true);
2743         $this->assertEquals(false, strpos($output, '50.0'));
2745         // Make sure the grade isn't pushed to the gradebook.
2746         $grades = $assign->get_user_grades_for_gradebook($student->id);
2747         $this->assertEmpty($grades);
2749         // Mark the submission and set to released.
2750         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2751                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
2752             ]);
2754         // Check the student can see the grade.
2755         $this->setUser($student);
2756         $output = $assign->view_student_summary($student, true);
2757         $this->assertNotEquals(false, strpos($output, '50.0'));
2759         // Make sure the grade is pushed to the gradebook.
2760         $grades = $assign->get_user_grades_for_gradebook($student->id);
2761         $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
2762     }
2764     /**
2765      * Test that a student allocated a specific marker is only shown to that marker.
2766      */
2767     public function test_markerallocation() {
2768         global $PAGE;
2770         $this->resetAfterTest();
2771         $course = $this->getDataGenerator()->create_course();
2772         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2773         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2774         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2776         $assign = $this->create_instance($course, [
2777                 'markingworkflow' => 1,
2778                 'markingallocation' => 1
2779             ]);
2781         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2783         // Allocate marker to submission.
2784         $this->mark_submission($teacher, $assign, $student, null, [
2785             'allocatedmarker' => $teacher->id,
2786         ]);
2788         // Check the allocated marker can view the submission.
2789         $this->setUser($teacher);
2790         $users = $assign->list_participants(0, true);
2791         $this->assertEquals(1, count($users));
2792         $this->assertTrue(isset($users[$student->id]));
2794         $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2795         $context = context_module::instance($cm->id);
2796         $assign = new mod_assign_testable_assign($context, $cm, $course);
2798         // Check that other teachers can't view this submission.
2799         $this->setUser($otherteacher);
2800         $users = $assign->list_participants(0, true);
2801         $this->assertEquals(0, count($users));
2802     }
2804     /**
2805      * Ensure that a teacher cannot submit for students as standard.
2806      */
2807     public function test_teacher_submit_for_student() {
2808         global $PAGE;
2810         $this->resetAfterTest();
2811         $course = $this->getDataGenerator()->create_course();
2812         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2813         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2815         $assign = $this->create_instance($course, [
2816             'assignsubmission_onlinetext_enabled' => 1,
2817             'submissiondrafts' => 1,
2818         ]);
2820         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2822         // Add a submission but do not submit.
2823         $this->add_submission($student, $assign, 'Student submission text');
2825         $this->setUser($student);
2826         $output = $assign->view_student_summary($student, true);
2827         $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2829         // Check that a teacher can not edit the submission as they do not have the capability.
2830         $this->setUser($teacher);
2831         $this->expectException('moodle_exception');
2832         $this->expectExceptionMessage('error/nopermission');
2833         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2834     }
2836     /**
2837      * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
2838      */
2839     public function test_teacher_submit_for_student_with_capability() {
2840         global $PAGE;
2842         $this->resetAfterTest();
2843         $course = $this->getDataGenerator()->create_course();
2844         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2845         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2846         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2848         $assign = $this->create_instance($course, [
2849             'assignsubmission_onlinetext_enabled' => 1,
2850             'submissiondrafts' => 1,
2851         ]);
2853         // Add the required capability.
2854         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2855         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2856         role_assign($roleid, $teacher->id, $assign->get_context()->id);
2857         accesslib_clear_all_caches_for_unit_testing();
2859         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2861         // Add a submission but do not submit.
2862         $this->add_submission($student, $assign, 'Student submission text');
2864         $this->setUser($student);
2865         $output = $assign->view_student_summary($student, true);
2866         $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2868         // Check that a teacher can edit the submission.
2869         $this->setUser($teacher);
2870         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2872         $this->setUser($student);
2873         $output = $assign->view_student_summary($student, true);
2874         $this->assertStringNotContainsString('Student submission text', $output, 'Contains student submission text');
2875         $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains teacher edited submission text');
2877         // Check that the teacher can submit the students work.
2878         $this->setUser($teacher);
2879         $this->submit_for_grading($student, $assign, [], false);
2881         // Revert to draft so the student can edit it.
2882         $assign->revert_to_draft($student->id);
2884         $this->setUser($student);
2886         // Check that the submission text was saved.
2887         $output = $assign->view_student_summary($student, true);
2888         $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains student submission text');
2890         // Check that the student can submit their work.
2891         $this->submit_for_grading($student, $assign, []);
2893         $output = $assign->view_student_summary($student, true);
2894         $this->assertStringNotContainsString(get_string('addsubmission', 'assign'), $output);
2896         // An editing teacher without the extra role should still be able to revert to draft.
2897         $this->setUser($otherteacher);
2899         // Revert to draft so the submission is editable.
2900         $assign->revert_to_draft($student->id);
2901     }
2903     /**
2904      * Ensure that disabling submit after the cutoff date works as expected.
2905      */
2906     public function test_disable_submit_after_cutoff_date() {
2907         global $PAGE;
2909         $this->resetAfterTest();
2910         $course = $this->getDataGenerator()->create_course();
2911         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2913         $now = time();
2914         $tomorrow = $now + DAYSECS;
2915         $lastweek = $now - (7 * DAYSECS);
2916         $yesterday = $now - DAYSECS;
2918         $this->setAdminUser();
2919         $assign = $this->create_instance($course, [
2920                 'duedate' => $yesterday,
2921                 'cutoffdate' => $tomorrow,
2922                 'assignsubmission_onlinetext_enabled' => 1,
2923             ]);
2925         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2927         // Student should be able to see an add submission button.
2928         $this->setUser($student);
2929         $output = $assign->view_student_summary($student, true);
2930         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2932         // Add a submission but don't submit now.
2933         $this->add_submission($student, $assign);
2935         // Create another instance with cut-off and due-date already passed.
2936         $this->setAdminUser();
2937         $assign = $this->create_instance($course, [
2938                 'duedate' => $lastweek,
2939                 'cutoffdate' => $yesterday,
2940                 'assignsubmission_onlinetext_enabled' => 1,
2941             ]);
2943         $this->setUser($student);
2944         $output = $assign->view_student_summary($student, true);
2945         $this->assertStringNotContainsString($output, get_string('editsubmission', 'assign'),
2946                                  'Should not be able to edit after cutoff date.');
2947         $this->assertStringNotContainsString($output, get_string('submitassignment', 'assign'),
2948                                  'Should not be able to submit after cutoff date.');
2949     }
2951     /**
2952      * Testing for submission comment plugin settings.
2953      *
2954      * @dataProvider submission_plugin_settings_provider
2955      * @param   bool    $globalenabled
2956      * @param   array   $instanceconfig
2957      * @param   bool    $isenabled
2958      */
2959     public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled) {
2960         global $CFG;
2962         $this->resetAfterTest();
2963         $course = $this->getDataGenerator()->create_course();
2965         $CFG->usecomments = $globalenabled;
2966         $assign = $this->create_instance($course, $instanceconfig);
2967         $plugin = $assign->get_submission_plugin_by_type('comments');
2968         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
2969     }
2971     public function submission_plugin_settings_provider() {
2972         return [
2973             'CFG->usecomments true, empty config => Enabled by default' => [
2974                 true,
2975                 [],
2976                 true,
2977             ],
2978             'CFG->usecomments true, config enabled => Comments enabled' => [
2979                 true,
2980                 [
2981                     'assignsubmission_comments_enabled' => 1,
2982                 ],
2983                 true,
2984             ],
2985             'CFG->usecomments true, config idisabled => Comments enabled' => [
2986                 true,
2987                 [
2988                     'assignsubmission_comments_enabled' => 0,
2989                 ],
2990                 true,
2991             ],
2992             'CFG->usecomments false, empty config => Disabled by default' => [
2993                 false,
2994                 [],
2995                 false,
2996             ],
2997             'CFG->usecomments false, config enabled => Comments disabled' => [
2998                 false,
2999                 [
3000                     'assignsubmission_comments_enabled' => 1,
3001                 ],
3002                 false,
3003             ],
3004             'CFG->usecomments false, config disabled => Comments disabled' => [
3005                 false,
3006                 [
3007                     'assignsubmission_comments_enabled' => 0,
3008                 ],
3009                 false,
3010             ],
3011         ];
3012     }
3014     /**
3015      * Testing for comment inline settings
3016      */
3017     public function test_feedback_comment_commentinline() {
3018         global $CFG, $USER;
3020         $this->resetAfterTest();
3021         $course = $this->getDataGenerator()->create_course();
3022         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3023         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3025         $sourcetext = "Hello!
3027 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3029 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3030 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3032 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
3033 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
3034 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
3035 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
3036 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
3037 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
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         // We need to get the URL these will be transformed to.
3067         $context = context_user::instance($USER->id);
3068         $itemid = $data->assignfeedbackcomments_editor['itemid'];
3069         $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid;
3071         // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
3072         $filteredtext = "Hello!
3074 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3076 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3077 Plugin url outside a tag: $url/logo-240x60.gif
3079 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3080 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3081 Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" />
3082 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" />
3083 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a>
3084 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3085 ";
3087         $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3088     }
3090     /**
3091      * Testing for feedback comment plugin settings.
3092      *
3093      * @dataProvider feedback_plugin_settings_provider
3094      * @param   array   $instanceconfig
3095      * @param   bool    $isenabled
3096      */
3097     public function test_feedback_plugin_settings($instanceconfig, $isenabled) {
3098         $this->resetAfterTest();
3099         $course = $this->getDataGenerator()->create_course();
3101         $assign = $this->create_instance($course, $instanceconfig);
3102         $plugin = $assign->get_feedback_plugin_by_type('comments');
3103         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3104     }
3106     public function feedback_plugin_settings_provider() {
3107         return [
3108             'No configuration => disabled' => [
3109                 [],
3110                 false,
3111             ],
3112             'Actively disabled' => [
3113                 [
3114                     'assignfeedback_comments_enabled' => 0,
3115                 ],
3116                 false,
3117             ],
3118             'Actively enabled' => [
3119                 [
3120                     'assignfeedback_comments_enabled' => 1,
3121                 ],
3122                 true,
3123             ],
3124         ];
3125     }
3127     /**
3128      * Testing if gradebook feedback plugin is enabled.
3129      */
3130     public function test_is_gradebook_feedback_enabled() {
3131         $this->resetAfterTest();
3132         $course = $this->getDataGenerator()->create_course();
3133         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3134         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3136         $adminconfig = get_config('assign');
3137         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3139         // Create assignment with gradebook feedback enabled and grade = 0.
3140         $assign = $this->create_instance($course, [
3141                 "{$gradebookplugin}_enabled" => 1,
3142                 'grades' => 0,
3143             ]);
3145         // Get gradebook feedback plugin.
3146         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3147         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3148         $this->assertEquals(1, $plugin->is_enabled('enabled'));
3149         $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3150     }
3152     /**
3153      * Testing if gradebook feedback plugin is disabled.
3154      */
3155     public function test_is_gradebook_feedback_disabled() {
3156         $this->resetAfterTest();
3157         $course = $this->getDataGenerator()->create_course();
3158         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3159         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3161         $adminconfig = get_config('assign');
3162         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3164         // Create assignment with gradebook feedback disabled and grade = 0.
3165         $assign = $this->create_instance($course, [
3166                 "{$gradebookplugin}_enabled" => 0,
3167                 'grades' => 0,
3168             ]);
3170         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3171         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3172         $this->assertEquals(0, $plugin->is_enabled('enabled'));
3173     }
3175     /**
3176      * Testing can_edit_submission.
3177      */
3178     public function test_can_edit_submission() {
3179         $this->resetAfterTest();
3180         $course = $this->getDataGenerator()->create_course();
3181         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3182         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3183         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3185         $assign = $this->create_instance($course, [
3186                 'assignsubmission_onlinetext_enabled' => 1,
3187                 'submissiondrafts' => 1,
3188             ]);
3190         // Check student can edit their own submission.
3191         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3193         // Check student cannot edit others submission.
3194         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3196         // Check teacher cannot (by default) edit a students submission.
3197         $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3198     }
3200     /**
3201      * Testing can_edit_submission with the editothersubmission capability.
3202      */
3203     public function test_can_edit_submission_with_editothersubmission() {
3204         $this->resetAfterTest();
3205         $course = $this->getDataGenerator()->create_course();
3206         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3207         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3208         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3210         $assign = $this->create_instance($course, [
3211                 'assignsubmission_onlinetext_enabled' => 1,
3212                 'submissiondrafts' => 1,
3213             ]);
3215         // Add the required capability to edit a student submission.
3216         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3217         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3218         role_assign($roleid, $teacher->id, $assign->get_context()->id);
3219         accesslib_clear_all_caches_for_unit_testing();
3221         // Check student can edit their own submission.
3222         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3224         // Check student cannot edit others submission.
3225         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3227         // Retest - should now have access.
3228         $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3229     }
3231     /**
3232      * Testing can_edit_submission
3233      */
3234     public function test_can_edit_submission_separategroups() {
3235         $this->resetAfterTest();
3236         $course = $this->getDataGenerator()->create_course();
3237         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3239         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3240         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3241         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3242         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3244         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3245         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3246         groups_assign_grouping($grouping->id, $group1->id);
3247         groups_add_member($group1, $student1);
3248         groups_add_member($group1, $student2);
3250         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3251         groups_assign_grouping($grouping->id, $group2->id);
3252         groups_add_member($group2, $student3);
3253         groups_add_member($group2, $student4);
3255         $assign = $this->create_instance($course, [
3256                 'assignsubmission_onlinetext_enabled' => 1,
3257                 'submissiondrafts' => 1,
3258                 'groupingid' => $grouping->id,
3259                 'groupmode' => SEPARATEGROUPS,
3260             ]);
3262         // Verify a student does not have the ability to edit submissions for other users.
3263         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3264         $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id));
3265         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3266         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3267     }
3269     /**
3270      * Testing can_edit_submission
3271      */
3272     public function test_can_edit_submission_separategroups_with_editothersubmission() {
3273         $this->resetAfterTest();
3274         $course = $this->getDataGenerator()->create_course();
3275         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3277         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3278         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3279         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3280         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3282         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3283         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3284         groups_assign_grouping($grouping->id, $group1->id);
3285         groups_add_member($group1, $student1);
3286         groups_add_member($group1, $student2);
3288         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3289         groups_assign_grouping($grouping->id, $group2->id);
3290         groups_add_member($group2, $student3);
3291         groups_add_member($group2, $student4);
3293         $assign = $this->create_instance($course, [
3294                 'assignsubmission_onlinetext_enabled' => 1,
3295                 'submissiondrafts' => 1,
3296                 'groupingid' => $grouping->id,
3297                 'groupmode' => SEPARATEGROUPS,
3298             ]);
3300         // Add the capability to the new assignment for student 1.
3301         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3302         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3303         role_assign($roleid, $student1->id, $assign->get_context()->id);
3304         accesslib_clear_all_caches_for_unit_testing();
3306         // Verify student1 has the ability to edit submissions for other users in their group, but not other groups.
3307         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3308         $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id));
3309         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3310         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));