MDL-55609 mod_assign: Remove shared setUp for all tests
[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($returnparams, $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 submissions with extension date.
194      */
195     public function test_gradingtable_extension_due_date() {
196         global $PAGE;
198         $this->resetAfterTest();
199         $course = $this->getDataGenerator()->create_course();
200         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
201         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
203         // Setup the assignment.
204         $this->setUser($teacher);
205         $time = time();
206         $assign = $this->create_instance($course, [
207                 'assignsubmission_onlinetext_enabled' => 1,
208                 'duedate' => time() - (4 * DAYSECS),
209             ]);
210         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
211             'id' => $assign->get_course_module()->id,
212             'action' => 'grading',
213         )));
215         // Check that the assignment is late.
216         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
217         $output = $assign->get_renderer()->render($gradingtable);
218         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
219         $this->assertContains(get_string('overdue', 'assign', format_time((4 * DAYSECS))), $output);
221         // Grant an extension.
222         $extendedtime = $time + (2 * DAYSECS);
223         $assign->testable_save_user_extension($student->id, $extendedtime);
224         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
225         $output = $assign->get_renderer()->render($gradingtable);
226         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
227         $this->assertContains(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
229         // Simulate a submission.
230         $this->setUser($student);
231         $submission = $assign->get_user_submission($student->id, true);
232         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
233         $assign->testable_update_submission($submission, $student->id, true, false);
234         $data = new stdClass();
235         $data->onlinetext_editor = [
236             'itemid' => file_get_unused_draft_itemid(),
237             'text' => 'Submission text',
238             'format' => FORMAT_MOODLE,
239         ];
240         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
241         $plugin->save($submission, $data);
243         // Verify output.
244         $this->setUser($teacher);
245         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
246         $output = $assign->get_renderer()->render($gradingtable);
247         $this->assertContains(get_string('submissionstatus_submitted', 'assign'), $output);
248         $this->assertContains(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
249     }
251     /**
252      * Test that late submissions with extension date calculate correctly.
253      */
254     public function test_gradingtable_extension_date_calculation_for_lateness() {
255         global $PAGE;
257         $this->resetAfterTest();
258         $course = $this->getDataGenerator()->create_course();
259         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
260         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
262         // Setup the assignment.
263         $this->setUser($teacher);
264         $time = time();
265         $assign = $this->create_instance($course, [
266                 'assignsubmission_onlinetext_enabled' => 1,
267                 'duedate' => time() - (4 * DAYSECS),
268             ]);
269         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
270             'id' => $assign->get_course_module()->id,
271             'action' => 'grading',
272         )));
274         // Check that the assignment is late.
275         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
276         $output = $assign->get_renderer()->render($gradingtable);
277         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
278         $difftime = time() - $time;
279         $this->assertContains(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
281         // Grant an extension that is in the past.
282         $assign->testable_save_user_extension($student->id, $time - (2 * DAYSECS));
283         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
284         $output = $assign->get_renderer()->render($gradingtable);
285         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
286         $this->assertContains(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
287         $difftime = time() - $time;
288         $this->assertContains(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
290         // Simulate a submission.
291         $this->setUser($student);
292         $submission = $assign->get_user_submission($student->id, true);
293         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
294         $assign->testable_update_submission($submission, $student->id, true, false);
295         $data = new stdClass();
296         $data->onlinetext_editor = [
297             'itemid' => file_get_unused_draft_itemid(),
298             'text' => 'Submission text',
299             'format' => FORMAT_MOODLE,
300         ];
301         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
302         $plugin->save($submission, $data);
303         $submittedtime = time();
305         // Verify output.
306         $this->setUser($teacher);
307         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
308         $output = $assign->get_renderer()->render($gradingtable);
309         $this->assertContains(get_string('submissionstatus_submitted', 'assign'), $output);
310         $this->assertContains(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
312         $difftime = $submittedtime - $time;
313         $this->assertContains(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
314     }
316     public function test_gradingtable_status_rendering() {
317         global $PAGE;
319         $this->resetAfterTest();
320         $course = $this->getDataGenerator()->create_course();
321         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
322         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
324         // Setup the assignment.
325         $this->setUser($teacher);
326         $time = time();
327         $assign = $this->create_instance($course, [
328             'assignsubmission_onlinetext_enabled' => 1,
329             'duedate' => $time - (4 * DAYSECS),
330          ]);
331         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
332             'id' => $assign->get_course_module()->id,
333             'action' => 'grading',
334         )));
336         // Check that the assignment is late.
337         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
338         $output = $assign->get_renderer()->render($gradingtable);
339         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
340         $difftime = time() - $time;
341         $this->assertContains(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
343         // Simulate a student viewing the assignment without submitting.
344         $this->setUser($student);
345         $submission = $assign->get_user_submission($student->id, true);
346         $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
347         $assign->testable_update_submission($submission, $student->id, true, false);
348         $submittedtime = time();
350         // Verify output.
351         $this->setUser($teacher);
352         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
353         $output = $assign->get_renderer()->render($gradingtable);
354         $difftime = $submittedtime - $time;
355         $this->assertContains(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
357         $document = new DOMDocument();
358         @$document->loadHTML($output);
359         $xpath = new DOMXPath($document);
360         $this->assertEquals('', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])'));
361     }
363     /**
364      * Check that group submission information is rendered correctly in the
365      * grading table.
366      */
367     public function test_gradingtable_group_submissions_rendering() {
368         global $PAGE;
370         $this->resetAfterTest();
371         $course = $this->getDataGenerator()->create_course();
372         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
374         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
375         groups_add_member($group, $teacher);
377         $students = [];
379         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
380         $students[] = $student;
381         groups_add_member($group, $student);
383         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
384         $students[] = $student;
385         groups_add_member($group, $student);
387         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
388         $students[] = $student;
389         groups_add_member($group, $student);
391         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
392         $students[] = $student;
393         groups_add_member($group, $student);
395         // Verify group assignments.
396         $this->setUser($teacher);
397         $assign = $this->create_instance($course, [
398             'teamsubmission' => 1,
399             'assignsubmission_onlinetext_enabled' => 1,
400             'submissiondrafts' => 1,
401             'requireallteammemberssubmit' => 0,
402         ]);
403         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
404             'id' => $assign->get_course_module()->id,
405             'action' => 'grading',
406         )));
408         // Add a submission.
409         $this->setUser($student);
410         $data = new stdClass();
411         $data->onlinetext_editor = [
412             'itemid' => file_get_unused_draft_itemid(),
413             'text' => 'Submission text',
414             'format' => FORMAT_MOODLE,
415         ];
416         $notices = array();
417         $assign->save_submission($data, $notices);
419         $submission = $assign->get_group_submission($student->id, 0, true);
420         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
421         $assign->testable_update_submission($submission, $student->id, true, true);
423         // Check output.
424         $this->setUser($teacher);
425         $gradingtable = new assign_grading_table($assign, 4, '', 0, true);
426         $output = $assign->get_renderer()->render($gradingtable);
427         $document = new DOMDocument();
428         @$document->loadHTML($output);
429         $xpath = new DOMXPath($document);
431         // Check status.
432         $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c4"]/div[@class="submissionstatussubmitted"])'));
433         $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c4"]/div[@class="submissionstatussubmitted"])'));
435         // Check submission last modified date
436         $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])')));
437         $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c8"])')));
439         // Check group.
440         $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c5"])'));
441         $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c5"])'));
443         // Check submission text.
444         $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c9"]/div/div)'));
445         $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c9"]/div/div)'));
447         // Check comments can be made.
448         $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r0_c10"]//textarea)'));
449         $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r3_c10"]//textarea)'));
450     }
452     public function test_show_intro() {
453         $this->resetAfterTest();
454         $course = $this->getDataGenerator()->create_course();
455         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
457         // Test whether we are showing the intro at the correct times.
458         $this->setUser($teacher);
459         $assign = $this->create_instance($course, ['alwaysshowdescription' => 1]);
461         $this->assertEquals(true, $assign->testable_show_intro());
463         $tomorrow = time() + DAYSECS;
465         $assign = $this->create_instance($course, [
466                 'alwaysshowdescription' => 0,
467                 'allowsubmissionsfromdate' => $tomorrow,
468             ]);
469         $this->assertEquals(false, $assign->testable_show_intro());
470         $yesterday = time() - DAYSECS;
471         $assign = $this->create_instance($course, [
472                 'alwaysshowdescription' => 0,
473                 'allowsubmissionsfromdate' => $yesterday,
474             ]);
475         $this->assertEquals(true, $assign->testable_show_intro());
476     }
478     public function test_has_submissions_or_grades() {
479         $this->resetAfterTest();
480         $course = $this->getDataGenerator()->create_course();
481         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
482         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
484         $this->setUser($teacher);
485         $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
486         $instance = $assign->get_instance();
488         // Should start empty.
489         $this->assertEquals(false, $assign->has_submissions_or_grades());
491         // Simulate a submission.
492         $this->setUser($student);
493         $submission = $assign->get_user_submission($student->id, true);
495         // The submission is still new.
496         $this->assertEquals(false, $assign->has_submissions_or_grades());
498         // Submit the submission.
499         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
500         $assign->testable_update_submission($submission, $student->id, true, false);
501         $data = new stdClass();
502         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
503                                          'text'=>'Submission text',
504                                          'format'=>FORMAT_MOODLE);
505         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
506         $plugin->save($submission, $data);
508         // Now test again.
509         $this->assertEquals(true, $assign->has_submissions_or_grades());
510     }
512     public function test_delete_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);
521         // Simulate adding a grade.
522         $this->setUser($teacher);
523         $data = new stdClass();
524         $data->grade = '50.0';
525         $assign->testable_apply_grade_to_user($data, $student->id, 0);
527         // Now see if the data is in the gradebook.
528         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
530         $this->assertNotEquals(0, count($gradinginfo->items));
532         $assign->testable_delete_grades();
533         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
535         $this->assertEquals(0, count($gradinginfo->items));
536     }
538     public function test_delete_instance() {
539         $this->resetAfterTest();
540         $course = $this->getDataGenerator()->create_course();
541         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
542         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
544         $this->setUser($teacher);
545         $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
547         // Simulate adding a grade.
548         $this->setUser($teacher);
549         $data = new stdClass();
550         $data->grade = '50.0';
551         $assign->testable_apply_grade_to_user($data, $student->id, 0);
553         // Simulate a submission.
554         $this->add_submission($student, $assign);
556         // Now try and delete.
557         $this->setUser($teacher);
558         $this->assertEquals(true, $assign->delete_instance());
559     }
561     public function test_reset_userdata() {
562         global $DB;
564         $this->resetAfterTest();
565         $course = $this->getDataGenerator()->create_course();
566         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
567         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
569         $now = time();
570         $this->setUser($teacher);
571         $assign = $this->create_instance($course, [
572                 'assignsubmission_onlinetext_enabled' => 1,
573                 'duedate' => $now,
574             ]);
576         // Simulate adding a grade.
577         $this->add_submission($student, $assign);
578         $this->submit_for_grading($student, $assign);
579         $this->mark_submission($teacher, $assign, $student, 50.0);
581         // Simulate a submission.
582         $this->setUser($student);
583         $submission = $assign->get_user_submission($student->id, true);
584         $data = new stdClass();
585         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
586                                          'text'=>'Submission text',
587                                          'format'=>FORMAT_MOODLE);
588         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
589         $plugin->save($submission, $data);
591         $this->assertEquals(true, $assign->has_submissions_or_grades());
592         // Now try and reset.
593         $data = new stdClass();
594         $data->reset_assign_submissions = 1;
595         $data->reset_gradebook_grades = 1;
596         $data->reset_assign_user_overrides = 1;
597         $data->reset_assign_group_overrides = 1;
598         $data->courseid = $course->id;
599         $data->timeshift = DAYSECS;
600         $this->setUser($teacher);
601         $assign->reset_userdata($data);
602         $this->assertEquals(false, $assign->has_submissions_or_grades());
604         // Reload the instance data.
605         $instance = $DB->get_record('assign', array('id'=>$assign->get_instance()->id));
606         $this->assertEquals($now + DAYSECS, $instance->duedate);
608         // Test reset using assign_reset_userdata().
609         $assignduedate = $instance->duedate; // Keep old updated value for comparison.
610         $data->timeshift = (2 * DAYSECS);
611         assign_reset_userdata($data);
612         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
613         $this->assertEquals($assignduedate + (2 * DAYSECS), $instance->duedate);
615         // Create one more assignment and reset, make sure time shifted for previous assignment is not changed.
616         $assign2 = $this->create_instance($course, [
617                 'assignsubmission_onlinetext_enabled' => 1,
618                 'duedate' => $now,
619             ]);
620         $assignduedate = $instance->duedate;
621         $data->timeshift = 3*DAYSECS;
622         $assign2->reset_userdata($data);
623         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
624         $this->assertEquals($assignduedate, $instance->duedate);
625         $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
626         $this->assertEquals($now + 3*DAYSECS, $instance2->duedate);
628         // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date.
629         $assignduedate = $instance->duedate;
630         $assign2duedate = $instance2->duedate;
631         $data->timeshift = (4 * DAYSECS);
632         assign_reset_userdata($data);
633         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
634         $this->assertEquals($assignduedate + (4 * DAYSECS), $instance->duedate);
635         $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
636         $this->assertEquals($assign2duedate + (4 * DAYSECS), $instance2->duedate);
637     }
639     public function test_plugin_settings() {
640         global $DB;
642         $this->resetAfterTest();
644         $course = $this->getDataGenerator()->create_course();
645         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
647         $now = time();
648         $this->setUser($teacher);
649         $assign = $this->create_instance($course, [
650                 'assignsubmission_file_enabled' => 1,
651                 'assignsubmission_file_maxfiles' => 12,
652                 'assignsubmission_file_maxsizebytes' => 10,
653             ]);
655         $plugin = $assign->get_submission_plugin_by_type('file');
656         $this->assertEquals('12', $plugin->get_config('maxfilesubmissions'));
657     }
659     public function test_update_calendar() {
660         global $DB;
662         $this->resetAfterTest();
664         $course = $this->getDataGenerator()->create_course();
665         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
667         $this->setUser($teacher);
668         $userctx = context_user::instance($teacher->id)->id;
670         // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey.
671         $draftid = file_get_unused_draft_itemid();
672         $_REQUEST['introeditor'] = $draftid;
673         $_POST['introeditor'] = $draftid;
674         $_POST['sesskey'] = sesskey();
676         // Write links to a draft area.
677         $fakearealink1 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">link</a>', 'draftfile.php', $userctx,
678             'user', 'draft', $draftid);
679         $fakearealink2 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">new</a>', 'draftfile.php', $userctx,
680             'user', 'draft', $draftid);
682         // Create a new assignment with links to a draft area.
683         $now = time();
684         $assign = $this->create_instance($course, [
685                 'duedate' => $now,
686                 'intro' => $fakearealink1,
687                 'introformat' => FORMAT_HTML
688             ]);
690         // See if there is an event in the calendar.
691         $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id);
692         $event = $DB->get_record('event', $params);
693         $this->assertNotEmpty($event);
694         $this->assertSame('link', $event->description);     // The pluginfile links are removed.
696         // Make sure the same works when updating the assignment.
697         $instance = $assign->get_instance();
698         $instance->instance = $instance->id;
699         $instance->intro = $fakearealink2;
700         $instance->introformat = FORMAT_HTML;
701         $assign->update_instance($instance);
702         $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
703         $event = $DB->get_record('event', $params);
704         $this->assertNotEmpty($event);
705         $this->assertSame('new', $event->description);     // The pluginfile links are removed.
707         // Create an assignment with a description that should be hidden.
708         $assign = $this->create_instance($course, [
709                 'duedate' => $now + 160,
710                 'alwaysshowdescription' => false,
711                 'allowsubmissionsfromdate' => $now + 60,
712                 'intro' => 'Some text',
713             ]);
715         // Get the event from the calendar.
716         $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id);
717         $event = $DB->get_record('event', [
718                 'modulename' => 'assign',
719                 'instance' => $assign->get_instance()->id,
720             ]);
722         $this->assertEmpty($event->description);
724         // Change the allowsubmissionfromdate to the past - do this directly in the DB
725         // because if we call the assignment update method - it will update the calendar
726         // and we want to test that this works from cron.
727         $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id'=>$assign->get_instance()->id));
728         // Run cron to update the event in the calendar.
729         assign::cron();
730         $event = $DB->get_record('event', $params);
732         $this->assertContains('Some text', $event->description);
734     }
736     public function test_update_instance() {
737         global $DB;
739         $this->resetAfterTest();
741         $course = $this->getDataGenerator()->create_course();
742         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
744         $this->setUser($teacher);
745         $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
747         $now = time();
748         $instance = $assign->get_instance();
749         $instance->duedate = $now;
750         $instance->instance = $instance->id;
751         $instance->assignsubmission_onlinetext_enabled = 1;
753         $assign->update_instance($instance);
755         $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
756         $this->assertEquals($now, $instance->duedate);
757     }
759     public function test_cannot_submit_empty() {
760         global $PAGE;
762         $this->resetAfterTest();
764         $course = $this->getDataGenerator()->create_course();
765         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
767         $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
769         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
771         // Test you cannot see the submit button for an offline assignment regardless.
772         $this->setUser($student);
773         $output = $assign->view_student_summary($student, true);
774         $this->assertNotContains(get_string('submitassignment', 'assign'), $output, 'Can submit empty offline assignment');
775     }
777     public function test_cannot_submit_empty_no_submission() {
778         global $PAGE;
780         $this->resetAfterTest();
782         $course = $this->getDataGenerator()->create_course();
783         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
785         $assign = $this->create_instance($course, [
786             'submissiondrafts' => 1,
787             'assignsubmission_onlinetext_enabled' => 1,
788         ]);
790         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
792         // Test you cannot see the submit button for an online text assignment with no submission.
793         $this->setUser($student);
794         $output = $assign->view_student_summary($student, true);
795         $this->assertNotContains(get_string('submitassignment', 'assign'), $output, 'Cannot submit empty onlinetext assignment');
796     }
798     public function test_can_submit_with_submission() {
799         global $PAGE;
801         $this->resetAfterTest();
803         $course = $this->getDataGenerator()->create_course();
804         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
806         $assign = $this->create_instance($course, [
807             'submissiondrafts' => 1,
808             'assignsubmission_onlinetext_enabled' => 1,
809         ]);
811         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
813         // Add a draft.
814         $this->add_submission($student, $assign);
816         // Test you can see the submit button for an online text assignment with a submission.
817         $this->setUser($student);
818         $output = $assign->view_student_summary($student, true);
819         $this->assertContains(get_string('submitassignment', 'assign'), $output, 'Can submit non empty onlinetext assignment');
820     }
822     /**
823      * Test new_submission_empty
824      *
825      * We only test combinations of plugins here. Individual plugins are tested
826      * in their respective test files.
827      *
828      * @dataProvider test_new_submission_empty_testcases
829      * @param string $data The file submission data
830      * @param bool $expected The expected return value
831      */
832     public function test_new_submission_empty($data, $expected) {
833         $this->resetAfterTest();
835         $course = $this->getDataGenerator()->create_course();
836         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
838         $assign = $this->create_instance($course, [
839                 'assignsubmission_file_enabled' => 1,
840                 'assignsubmission_file_maxfiles' => 12,
841                 'assignsubmission_file_maxsizebytes' => 10,
842                 'assignsubmission_onlinetext_enabled' => 1,
843             ]);
844         $this->setUser($student);
845         $submission = new stdClass();
847         if ($data['file'] && isset($data['file']['filename'])) {
848             $itemid = file_get_unused_draft_itemid();
849             $submission->files_filemanager = $itemid;
850             $data['file'] += ['contextid' => context_user::instance($student->id)->id, 'itemid' => $itemid];
851             $fs = get_file_storage();
852             $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']);
853         }
855         if ($data['onlinetext']) {
856             $submission->onlinetext_editor = ['text' => $data['onlinetext']];
857         }
859         $result = $assign->new_submission_empty($submission);
860         $this->assertTrue($result === $expected);
861     }
863     /**
864      * Dataprovider for the test_new_submission_empty testcase
865      *
866      * @return array of testcases
867      */
868     public function test_new_submission_empty_testcases() {
869         return [
870             'With file and onlinetext' => [
871                 [
872                     'file' => [
873                         'component' => 'user',
874                         'filearea' => 'draft',
875                         'filepath' => '/',
876                         'filename' => 'not_a_virus.exe'
877                     ],
878                     'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu'
879                 ],
880                 false
881             ]
882         ];
883     }
885     public function test_list_participants() {
886         global $CFG;
888         $this->resetAfterTest();
890         $course = $this->getDataGenerator()->create_course();
891         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
893         // Create 10 students.
894         for ($i = 0; $i < 10; $i++) {
895             $this->getDataGenerator()->create_and_enrol($course, 'student');
896         }
898         $this->setUser($teacher);
899         $assign = $this->create_instance($course, ['grade' => 100]);
901         $this->assertCount(10, $assign->list_participants(null, true));
902     }
904     public function test_list_participants_activeenrol() {
905         global $CFG, $DB;
907         $this->resetAfterTest();
909         $course = $this->getDataGenerator()->create_course();
910         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
912         // Create 10 students.
913         for ($i = 0; $i < 10; $i++) {
914             $this->getDataGenerator()->create_and_enrol($course, 'student');
915         }
917         // Create 10 suspended students.
918         for ($i = 0; $i < 10; $i++) {
919             $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
920         }
922         $this->setUser($teacher);
923         set_user_preference('grade_report_showonlyactiveenrol', false);
924         $assign = $this->create_instance($course, ['grade' => 100]);
926         $this->assertCount(10, $assign->list_participants(null, true));
927     }
929     public function test_list_participants_with_group_restriction() {
930         global $CFG;
932         $this->resetAfterTest();
934         $course = $this->getDataGenerator()->create_course();
935         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
936         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
937         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
938         $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
940         // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group.
941         $CFG->enableavailability = true;
943         $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
944         $assign = $this->create_instance($course, [
945                 'grade' => 100,
946                 'availability' => json_encode(
947                     \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)])
948                 ),
949             ]);
951         groups_add_member($specialgroup, $student);
952         groups_add_member($specialgroup, $otherstudent);
953         $this->assertEquals(2, count($assign->list_participants(null, true)));
954     }
956     public function test_get_participant_user_not_exist() {
957         $this->resetAfterTest();
958         $course = $this->getDataGenerator()->create_course();
960         $assign = $this->create_instance($course);
961         $this->assertNull($assign->get_participant('-1'));
962     }
964     public function test_get_participant_not_enrolled() {
965         $this->resetAfterTest();
966         $course = $this->getDataGenerator()->create_course();
967         $assign = $this->create_instance($course);
969         $user = $this->getDataGenerator()->create_user();
970         $this->assertNull($assign->get_participant($user->id));
971     }
973     public function test_get_participant_no_submission() {
974         $this->resetAfterTest();
975         $course = $this->getDataGenerator()->create_course();
976         $assign = $this->create_instance($course);
977         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
979         $participant = $assign->get_participant($student->id);
981         $this->assertEquals($student->id, $participant->id);
982         $this->assertFalse($participant->submitted);
983         $this->assertFalse($participant->requiregrading);
984         $this->assertFalse($participant->grantedextension);
985     }
987     public function test_get_participant_granted_extension() {
988         $this->resetAfterTest();
989         $course = $this->getDataGenerator()->create_course();
990         $assign = $this->create_instance($course);
991         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
992         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
994         $this->setUser($teacher);
995         $assign->save_user_extension($student->id, time());
996         $participant = $assign->get_participant($student->id);
998         $this->assertEquals($student->id, $participant->id);
999         $this->assertFalse($participant->submitted);
1000         $this->assertFalse($participant->requiregrading);
1001         $this->assertTrue($participant->grantedextension);
1002     }
1004     public function test_get_participant_with_ungraded_submission() {
1005         $this->resetAfterTest();
1006         $course = $this->getDataGenerator()->create_course();
1007         $assign = $this->create_instance($course);
1008         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1009         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1011         // Simulate a submission.
1012         $this->add_submission($student, $assign);
1013         $this->submit_for_grading($student, $assign);
1015         $participant = $assign->get_participant($student->id);
1017         $this->assertEquals($student->id, $participant->id);
1018         $this->assertTrue($participant->submitted);
1019         $this->assertTrue($participant->requiregrading);
1020         $this->assertFalse($participant->grantedextension);
1021     }
1023     public function test_get_participant_with_graded_submission() {
1024         $this->resetAfterTest();
1025         $course = $this->getDataGenerator()->create_course();
1026         $assign = $this->create_instance($course);
1027         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1028         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1030         // Simulate a submission.
1031         $this->add_submission($student, $assign);
1032         $this->submit_for_grading($student, $assign);
1034         // TODO Find a way to kill this waitForSecond
1035         // This is to make sure the grade happens after the submission because
1036         // we have no control over the timemodified values.
1037         $this->waitForSecond();
1038         $this->mark_submission($teacher, $assign, $student, 50.0);
1040         $data = new stdClass();
1041         $data->grade = '50.0';
1042         $assign->testable_apply_grade_to_user($data, $student->id, 0);
1044         $participant = $assign->get_participant($student->id);
1046         $this->assertEquals($student->id, $participant->id);
1047         $this->assertTrue($participant->submitted);
1048         $this->assertFalse($participant->requiregrading);
1049         $this->assertFalse($participant->grantedextension);
1050     }
1052     /**
1053      * No active group and non-group submissions disallowed => 2 groups.
1054      */
1055     public function test_count_teams_no_active_non_group_allowed() {
1056         $this->resetAfterTest();
1058         $course = $this->getDataGenerator()->create_course();
1059         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1060         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1061         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1063         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1064         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1065         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1066         groups_add_member($group1, $student1);
1067         groups_add_member($group2, $student2);
1069         $this->setUser($teacher);
1070         $assign = $this->create_instance($course, ['teamsubmission' => 1]);
1072         $this->assertEquals(2, $assign->count_teams());
1073     }
1075     /**
1076      * No active group and non group submissions allowed => 2 groups + the default one.
1077      */
1078     public function test_count_teams_non_group_allowed() {
1079         $this->resetAfterTest();
1081         $course = $this->getDataGenerator()->create_course();
1082         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1083         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1084         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1085         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1087         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1088         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1090         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1091         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1092         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1094         groups_add_member($group1, $student1);
1095         groups_add_member($group2, $student2);
1097         $assign = $this->create_instance($course, [
1098                 'teamsubmission' => 1,
1099                 'teamsubmissiongroupingid' => $grouping->id,
1100                 'preventsubmissionnotingroup' => false,
1101             ]);
1103         $this->setUser($teacher);
1104         $this->assertEquals(3, $assign->count_teams());
1106         // Active group only.
1107         $this->assertEquals(1, $assign->count_teams($group1->id));
1108         $this->assertEquals(1, $assign->count_teams($group2->id));
1109     }
1111     /**
1112      * Active group => just selected one.
1113      */
1114     public function test_count_teams_no_active_group() {
1115         $this->resetAfterTest();
1117         $course = $this->getDataGenerator()->create_course();
1118         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1119         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1120         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1121         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1123         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1124         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1126         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1127         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1128         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1130         groups_add_member($group1, $student1);
1131         groups_add_member($group2, $student2);
1133         $assign = $this->create_instance($course, [
1134                 'teamsubmission' => 1,
1135                 'preventsubmissionnotingroup' => true,
1136             ]);
1138         $this->setUser($teacher);
1139         $this->assertEquals(2, $assign->count_teams());
1141         // Active group only.
1142         $this->assertEquals(1, $assign->count_teams($group1->id));
1143         $this->assertEquals(1, $assign->count_teams($group2->id));
1144     }
1146     /**
1147      * Active group => just selected one.
1148      */
1149     public function test_count_teams_groups_only() {
1150         $this->resetAfterTest();
1152         $course = $this->getDataGenerator()->create_course();
1153         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1155         $assign = $this->create_instance($course, [
1156                 'teamsubmission' => 1,
1157                 'teamsubmissiongroupingid' => $grouping->id,
1158                 'preventsubmissionnotingroup' => false,
1159             ]);
1160         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1162         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1163         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1164         groups_add_member($group1, $student1);
1166         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1167         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1168         groups_add_member($group2, $student2);
1170         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1171         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1173         $this->setUser($teacher);
1175         $assign = $this->create_instance($course, [
1176                 'teamsubmission' => 1,
1177                 'preventsubmissionnotingroup' => true,
1178             ]);
1179         $this->assertEquals(2, $assign->count_teams());
1180     }
1182     public function test_submit_to_default_group() {
1183         global $DB, $SESSION;
1185         $this->resetAfterTest();
1187         $course = $this->getDataGenerator()->create_course();
1188         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1189         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1191         $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1192         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1194         $assign = $this->create_instance($course, [
1195                 'teamsubmission' => 1,
1196                 'assignsubmission_onlinetext_enabled' => 1,
1197                 'submissiondrafts' => 0,
1198                 'groupmode' => VISIBLEGROUPS,
1199             ]);
1201         $usergroup = $assign->get_submission_group($student->id);
1202         $this->assertFalse($usergroup, 'New student is in default group');
1204         // Add a submission.
1205         $this->add_submission($student, $assign);
1206         $this->submit_for_grading($student, $assign);
1208         // Set active groups to all groups.
1209         $this->setUser($teacher);
1210         $SESSION->activegroup[$course->id]['aag'][0] = 0;
1211         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1213         // Set an active group.
1214         $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id;
1215         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1216     }
1218     public function test_count_submissions_no_draft() {
1219         $this->resetAfterTest();
1221         $course = $this->getDataGenerator()->create_course();
1222         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1223         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1225         $assign = $this->create_instance($course, [
1226                 'assignsubmission_onlinetext_enabled' => 1,
1227             ]);
1229         $assign->get_user_submission($student->id, true);
1231         // Note: Drafts count as a submission.
1232         $this->assertEquals(0, $assign->count_grades());
1233         $this->assertEquals(0, $assign->count_submissions());
1234         $this->assertEquals(1, $assign->count_submissions(true));
1235         $this->assertEquals(0, $assign->count_submissions_need_grading());
1236         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1237         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1238         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1239         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1240     }
1242     public function test_count_submissions_draft() {
1243         $this->resetAfterTest();
1245         $course = $this->getDataGenerator()->create_course();
1246         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1247         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1249         $assign = $this->create_instance($course, [
1250                 'assignsubmission_onlinetext_enabled' => 1,
1251             ]);
1253         $this->add_submission($student, $assign);
1255         // Note: Drafts count as a submission.
1256         $this->assertEquals(0, $assign->count_grades());
1257         $this->assertEquals(1, $assign->count_submissions());
1258         $this->assertEquals(1, $assign->count_submissions(true));
1259         $this->assertEquals(0, $assign->count_submissions_need_grading());
1260         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1261         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1262         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1263         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1264     }
1266     public function test_count_submissions_submitted() {
1267         global $SESSION;
1269         $this->resetAfterTest();
1271         $course = $this->getDataGenerator()->create_course();
1272         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1273         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1275         $assign = $this->create_instance($course, [
1276                 'assignsubmission_onlinetext_enabled' => 1,
1277             ]);
1279         $this->add_submission($student, $assign);
1280         $this->submit_for_grading($student, $assign);
1282         $this->assertEquals(0, $assign->count_grades());
1283         $this->assertEquals(1, $assign->count_submissions());
1284         $this->assertEquals(1, $assign->count_submissions(true));
1285         $this->assertEquals(1, $assign->count_submissions_need_grading());
1286         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1287         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1288         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1289         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1290     }
1292     public function test_count_submissions_graded() {
1293         $this->resetAfterTest();
1295         $course = $this->getDataGenerator()->create_course();
1296         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1297         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1299         $assign = $this->create_instance($course, [
1300                 'assignsubmission_onlinetext_enabled' => 1,
1301             ]);
1303         $this->add_submission($student, $assign);
1304         $this->submit_for_grading($student, $assign);
1305         $this->waitForSecond();
1306         $this->mark_submission($teacher, $assign, $student, 50.0);
1308         // Although it has been graded, it is still marked as submitted.
1309         $this->assertEquals(1, $assign->count_grades());
1310         $this->assertEquals(1, $assign->count_submissions());
1311         $this->assertEquals(1, $assign->count_submissions(true));
1312         $this->assertEquals(0, $assign->count_submissions_need_grading());
1313         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1314         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1315         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1316         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1317     }
1319     public function test_count_submissions_graded_group() {
1320         global $SESSION;
1322         $this->resetAfterTest();
1324         $course = $this->getDataGenerator()->create_course();
1325         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1326         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1327         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1328         $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1329         groups_add_member($group, $student);
1331         $assign = $this->create_instance($course, [
1332                 'assignsubmission_onlinetext_enabled' => 1,
1333                 'groupmode' => VISIBLEGROUPS,
1334             ]);
1336         $this->add_submission($student, $assign);
1337         $this->submit_for_grading($student, $assign);
1339         // The user should still be listed when fetching all groups.
1340         $this->setUser($teacher);
1341         $SESSION->activegroup[$course->id]['aag'][0] = 0;
1342         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1344         // The user should still be listed when fetching just their group.
1345         $SESSION->activegroup[$course->id]['aag'][0] = $group->id;
1346         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1348         // The user should still be listed when fetching just their group.
1349         $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id;
1350         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1351     }
1353     // TODO
1354     public function x_test_count_submissions_for_team() {
1355         $this->resetAfterTest();
1357         $course = $this->getDataGenerator()->create_course();
1358         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1359         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1360         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1361         $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1362         groups_add_member($group, $student);
1364         $assign = $this->create_instance($course, [
1365                 'assignsubmission_onlinetext_enabled' => 1,
1366                 'teamsubmission' => 1,
1367             ]);
1369         // Add a graded submission.
1370         $this->add_submission($student, $assign);
1374         // Simulate adding a grade.
1375         $this->setUser($teacher);
1376         $data = new stdClass();
1377         $data->grade = '50.0';
1378         $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
1380         // Simulate a submission.
1381         $this->setUser($this->extrastudents[1]);
1382         $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
1383         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1384         $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1385         $data = new stdClass();
1386         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1387                                          'text' => 'Submission text',
1388                                          'format' => FORMAT_MOODLE);
1389         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1390         $plugin->save($submission, $data);
1392         // Simulate a submission.
1393         $this->setUser($this->extrastudents[2]);
1394         $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
1395         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1396         $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1397         $data = new stdClass();
1398         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1399                                          'text' => 'Submission text',
1400                                          'format' => FORMAT_MOODLE);
1401         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1402         $plugin->save($submission, $data);
1404         // Simulate a submission.
1405         $this->setUser($this->extrastudents[3]);
1406         $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
1407         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1408         $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1409         $data = new stdClass();
1410         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1411                                          'text' => 'Submission text',
1412                                          'format' => FORMAT_MOODLE);
1413         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1414         $plugin->save($submission, $data);
1416         // Simulate adding a grade.
1417         $this->setUser($teacher);
1418         $data = new stdClass();
1419         $data->grade = '50.0';
1420         $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1421         $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1423         // Create a new submission with status NEW.
1424         $this->setUser($this->extrastudents[4]);
1425         $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
1427         $this->assertEquals(2, $assign->count_grades());
1428         $this->assertEquals(4, $assign->count_submissions());
1429         $this->assertEquals(5, $assign->count_submissions(true));
1430         $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1431         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1432     }
1434     public function test_get_grading_userid_list_only_active() {
1435         $this->resetAfterTest();
1437         $course = $this->getDataGenerator()->create_course();
1438         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1439         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1440         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1442         $this->setUser($teacher);
1444         $assign = $this->create_instance($course);
1445         $this->assertCount(1, $assign->testable_get_grading_userid_list());
1446     }
1448     public function test_get_grading_userid_list_all() {
1449         $this->resetAfterTest();
1451         $course = $this->getDataGenerator()->create_course();
1452         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1453         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1454         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1456         $this->setUser($teacher);
1457         set_user_preference('grade_report_showonlyactiveenrol', false);
1459         $assign = $this->create_instance($course);
1460         $this->assertCount(2, $assign->testable_get_grading_userid_list());
1461     }
1463     public function test_cron() {
1464         $this->resetAfterTest();
1466         // First run cron so there are no messages waiting to be sent (from other tests).
1467         cron_setup_user();
1468         assign::cron();
1470         $course = $this->getDataGenerator()->create_course();
1471         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1472         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1474         // Now create an assignment and add some feedback.
1475         $this->setUser($teacher);
1476         $assign = $this->create_instance($course, [
1477                 'sendstudentnotifications' => 1,
1478             ]);
1480         $this->add_submission($student, $assign);
1481         $this->submit_for_grading($student, $assign);
1482         $this->mark_submission($teacher, $assign, $student, 50.0);
1484         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1485         cron_setup_user();
1486         $sink = $this->redirectMessages();
1487         assign::cron();
1488         $messages = $sink->get_messages();
1490         $this->assertEquals(1, count($messages));
1491         $this->assertEquals(1, $messages[0]->notification);
1492         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1493     }
1495     public function test_cron_without_notifications() {
1496         $this->resetAfterTest();
1498         // First run cron so there are no messages waiting to be sent (from other tests).
1499         cron_setup_user();
1500         assign::cron();
1502         $course = $this->getDataGenerator()->create_course();
1503         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1504         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1506         // Now create an assignment and add some feedback.
1507         $this->setUser($teacher);
1508         $assign = $this->create_instance($course, [
1509                 'sendstudentnotifications' => 1,
1510             ]);
1512         $this->add_submission($student, $assign);
1513         $this->submit_for_grading($student, $assign);
1514         $this->mark_submission($teacher, $assign, $student, 50.0, [
1515                 'sendstudentnotifications' => 0,
1516             ]);
1518         cron_setup_user();
1519         $sink = $this->redirectMessages();
1520         assign::cron();
1521         $messages = $sink->get_messages();
1523         $this->assertEquals(0, count($messages));
1524     }
1526     public function test_cron_regraded() {
1527         $this->resetAfterTest();
1529         // First run cron so there are no messages waiting to be sent (from other tests).
1530         cron_setup_user();
1531         assign::cron();
1533         $course = $this->getDataGenerator()->create_course();
1534         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1535         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1537         // Now create an assignment and add some feedback.
1538         $this->setUser($teacher);
1539         $assign = $this->create_instance($course, [
1540                 'sendstudentnotifications' => 1,
1541             ]);
1543         $this->add_submission($student, $assign);
1544         $this->submit_for_grading($student, $assign);
1545         $this->mark_submission($teacher, $assign, $student, 50.0);
1547         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1548         cron_setup_user();
1549         assign::cron();
1551         // Regrade.
1552         $this->mark_submission($teacher, $assign, $student, 50.0);
1554         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1555         cron_setup_user();
1556         $sink = $this->redirectMessages();
1557         assign::cron();
1558         $messages = $sink->get_messages();
1560         $this->assertEquals(1, count($messages));
1561         $this->assertEquals(1, $messages[0]->notification);
1562         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1563     }
1565     /**
1566      * Test delivery of grade notifications as controlled by marking workflow.
1567      */
1568     public function test_markingworkflow_cron() {
1569         $this->resetAfterTest();
1571         // First run cron so there are no messages waiting to be sent (from other tests).
1572         cron_setup_user();
1573         assign::cron();
1575         $course = $this->getDataGenerator()->create_course();
1576         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1577         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1579         // Now create an assignment and add some feedback.
1580         $this->setUser($teacher);
1581         $assign = $this->create_instance($course, [
1582                 'sendstudentnotifications' => 1,
1583                 'markingworkflow' => 1,
1584             ]);
1586         // Mark a submission but set the workflowstate to an unreleased state.
1587         // This should not trigger a notification.
1588         $this->add_submission($student, $assign);
1589         $this->submit_for_grading($student, $assign);
1590         $this->mark_submission($teacher, $assign, $student, 50.0, [
1591                 'sendstudentnotifications' => 1,
1592                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1593             ]);
1595         cron_setup_user();
1596         $sink = $this->redirectMessages();
1597         assign::cron();
1598         $messages = $sink->get_messages();
1600         $this->assertEquals(0, count($messages));
1602         // Transition to the released state.
1603         $this->setUser($teacher);
1604         $submission = $assign->get_user_submission($student->id, true);
1605         $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1606         $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1608         // Now run cron and see that one message was sent.
1609         cron_setup_user();
1610         $sink = $this->redirectMessages();
1611         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1612         assign::cron();
1613         $messages = $sink->get_messages();
1615         $this->assertEquals(1, count($messages));
1616         $this->assertEquals(1, $messages[0]->notification);
1617         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1618     }
1620     public function test_cron_message_includes_courseid() {
1621         $this->resetAfterTest();
1623         // First run cron so there are no messages waiting to be sent (from other tests).
1624         cron_setup_user();
1625         assign::cron();
1627         $course = $this->getDataGenerator()->create_course();
1628         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1629         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1631         // Now create an assignment and add some feedback.
1632         $this->setUser($teacher);
1633         $assign = $this->create_instance($course, [
1634                 'sendstudentnotifications' => 1,
1635             ]);
1637         // Mark a submission but set the workflowstate to an unreleased state.
1638         // This should not trigger a notification.
1639         $this->add_submission($student, $assign);
1640         $this->submit_for_grading($student, $assign);
1641         $this->mark_submission($teacher, $assign, $student);
1642         phpunit_util::stop_message_redirection();
1644         // Now run cron and see that one message was sent.
1645         cron_setup_user();
1646         $this->preventResetByRollback();
1647         $sink = $this->redirectEvents();
1648         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1649         assign::cron();
1651         $events = $sink->get_events();
1652         $event = reset($events);
1653         $this->assertInstanceOf('\core\event\notification_sent', $event);
1654         $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1655         $sink->close();
1656     }
1658     public function test_is_graded() {
1659         $this->resetAfterTest();
1661         $course = $this->getDataGenerator()->create_course();
1662         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1663         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1664         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1666         $assign = $this->create_instance($course);
1668         $this->add_submission($student, $assign);
1669         $this->submit_for_grading($student, $assign);
1670         $this->mark_submission($teacher, $assign, $student, 50.0);
1672         $this->setUser($teacher);
1673         $this->assertEquals(true, $assign->testable_is_graded($student->id));
1674         $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1675     }
1677     public function test_can_grade() {
1678         global $DB;
1680         $this->resetAfterTest();
1682         $course = $this->getDataGenerator()->create_course();
1683         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1684         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1686         $assign = $this->create_instance($course);
1688         $this->setUser($student);
1689         $this->assertEquals(false, $assign->can_grade());
1691         $this->setUser($teacher);
1692         $this->assertEquals(true, $assign->can_grade());
1694         // Test the viewgrades capability - without mod/assign:grade.
1695         $this->setUser($student);
1697         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1698         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1699         $this->assertEquals(false, $assign->can_grade());
1700     }
1702     public function test_can_view_submission() {
1703         global $DB;
1705         $this->resetAfterTest();
1707         $course = $this->getDataGenerator()->create_course();
1708         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1709         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1710         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1711         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1712         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1714         $assign = $this->create_instance($course);
1716         $this->setUser($student);
1717         $this->assertEquals(true, $assign->can_view_submission($student->id));
1718         $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1719         $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1721         $this->setUser($teacher);
1722         $this->assertEquals(true, $assign->can_view_submission($student->id));
1723         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1724         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1725         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1727         $this->setUser($editingteacher);
1728         $this->assertEquals(true, $assign->can_view_submission($student->id));
1729         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1730         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1731         $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1733         // Test the viewgrades capability - without mod/assign:grade.
1734         $this->setUser($student);
1735         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1736         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1737         $this->assertEquals(true, $assign->can_view_submission($student->id));
1738         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1739         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1740         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1741     }
1743     public function test_update_submission() {
1744         $this->resetAfterTest();
1746         $course = $this->getDataGenerator()->create_course();
1747         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1748         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1750         $assign = $this->create_instance($course);
1752         $this->add_submission($student, $assign);
1753         $submission = $assign->get_user_submission($student->id, 0);
1754         $assign->testable_update_submission($submission, $student->id, true, true);
1756         $this->setUser($teacher);
1758         // Verify the gradebook update.
1759         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1761         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1762         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1763     }
1765     public function test_update_submission_team() {
1766         $this->resetAfterTest();
1768         $course = $this->getDataGenerator()->create_course();
1769         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1770         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1772         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1773         groups_add_member($group, $student);
1775         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1776         groups_add_member($group, $otherstudent);
1778         $assign = $this->create_instance($course, [
1779                 'teamsubmission' => 1,
1780             ]);
1782         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1783         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1784         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1786         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1787         $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1788         $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1790         $this->add_submission($student, $assign);
1791         $submission = $assign->get_group_submission($student->id, 0, true);
1792         $assign->testable_update_submission($submission, $student->id, true, true);
1794         // Verify the gradebook update for the student.
1795         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1797         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1798         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1800         // Verify the gradebook update for the other student.
1801         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1803         $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1804         $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1805     }
1807     public function test_update_submission_suspended() {
1808         $this->resetAfterTest();
1810         $course = $this->getDataGenerator()->create_course();
1811         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1812         $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1814         $assign = $this->create_instance($course);
1816         $this->add_submission($student, $assign);
1817         $submission = $assign->get_user_submission($student->id, 0);
1818         $assign->testable_update_submission($submission, $student->id, true, false);
1820         $this->setUser($teacher);
1822         // Verify the gradebook update.
1823         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1825         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1826         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1827     }
1829     public function test_update_submission_blind() {
1830         $this->resetAfterTest();
1832         $course = $this->getDataGenerator()->create_course();
1833         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1834         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1836         $assign = $this->create_instance($course, [
1837                 'blindmarking' => 1,
1838             ]);
1840         $this->add_submission($student, $assign);
1841         $submission = $assign->get_user_submission($student->id, 0);
1842         $assign->testable_update_submission($submission, $student->id, true, false);
1844         // Verify the gradebook update.
1845         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1847         // The usermodified is not set because this is blind marked.
1848         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1849         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1850     }
1852     public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
1853         global $PAGE;
1855         $this->resetAfterTest();
1857         $course = $this->getDataGenerator()->create_course();
1858         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1859         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1861         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1862         groups_add_member($group, $student);
1864         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1865         groups_add_member($group, $otherstudent);
1867         $assign = $this->create_instance($course, [
1868                 'teamsubmission' => 1,
1869                 'assignsubmission_onlinetext_enabled' => 1,
1870                 'submissiondrafts' => 1,
1871                 'requireallteammemberssubmit' => 1,
1872             ]);
1874         // Now verify group assignments.
1875         $this->setUser($teacher);
1876         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1878         // Add a submission.
1879         $this->add_submission($student, $assign);
1881         // Check we can see the submit button.
1882         $this->setUser($student);
1883         $output = $assign->view_student_summary($student, true);
1884         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1886         $submission = $assign->get_group_submission($student->id, 0, true);
1887         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1888         $assign->testable_update_submission($submission, $student->id, true, true);
1890         // Check that the student does not see "Submit" button.
1891         $output = $assign->view_student_summary($student, true);
1892         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1894         // Change to another user in the same group.
1895         $this->setUser($otherstudent);
1896         $output = $assign->view_student_summary($otherstudent, true);
1897         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1899         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
1900         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1901         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
1902         $output = $assign->view_student_summary($otherstudent, true);
1903         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1904     }
1906     public function test_group_submissions_submit_for_marking() {
1907         global $PAGE;
1909         $this->resetAfterTest();
1911         $course = $this->getDataGenerator()->create_course();
1912         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1913         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1915         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1916         groups_add_member($group, $student);
1918         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1919         groups_add_member($group, $otherstudent);
1921         // Now verify group assignments.
1922         $this->setUser($teacher);
1923         $time = time();
1924         $assign = $this->create_instance($course, [
1925                 'teamsubmission' => 1,
1926                 'assignsubmission_onlinetext_enabled' => 1,
1927                 'submissiondrafts' => 1,
1928                 'requireallteammemberssubmit' => 0,
1929                 'duedate' => $time - (2 * DAYSECS),
1930             ]);
1931         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1933         // Add a submission.
1934         $this->add_submission($student, $assign);
1937         // Check we can see the submit button.
1938         $output = $assign->view_student_summary($student, true);
1939         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1940         $this->assertContains(get_string('timeremaining', 'assign'), $output);
1941         $difftime = time() - $time;
1942         $this->assertContains(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
1944         $submission = $assign->get_group_submission($student->id, 0, true);
1945         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1946         $assign->testable_update_submission($submission, $student->id, true, true);
1948         // Check that the student does not see "Submit" button.
1949         $output = $assign->view_student_summary($student, true);
1950         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1952         // Change to another user in the same group.
1953         $this->setUser($otherstudent);
1954         $output = $assign->view_student_summary($otherstudent, true);
1955         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1957         // Check that time remaining is not overdue.
1958         $this->assertContains(get_string('timeremaining', 'assign'), $output);
1959         $difftime = time() - $time;
1960         $this->assertContains(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
1962         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
1963         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1964         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
1965         $output = $assign->view_student_summary($otherstudent, true);
1966         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1967     }
1969     public function test_submissions_open() {
1970         global $DB;
1972         $this->resetAfterTest();
1974         $course = $this->getDataGenerator()->create_course();
1975         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1976         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1977         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1978         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1979         $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1981         $this->setAdminUser();
1983         $now = time();
1984         $tomorrow = $now + DAYSECS;
1985         $oneweek = $now + WEEKSECS;
1986         $yesterday = $now - DAYSECS;
1988         $assign = $this->create_instance($course);
1989         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
1991         $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
1992         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
1994         $assign = $this->create_instance($course, ['duedate' => $yesterday]);
1995         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
1997         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
1998         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2000         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2001         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2003         $assign->testable_save_user_extension($student->id, $tomorrow);
2004         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2006         $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2007         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2009         $this->setUser($student);
2010         $submission = $assign->get_user_submission($student->id, true);
2011         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2012         $assign->testable_update_submission($submission, $student->id, true, false);
2014         $this->setUser($teacher);
2015         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2016     }
2018     public function test_get_graders() {
2019         global $DB;
2021         $this->resetAfterTest();
2023         $course = $this->getDataGenerator()->create_course();
2024         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2025         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2026         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2028         $this->setAdminUser();
2030         // Create an assignment with no groups.
2031         $assign = $this->create_instance($course);
2032         $this->assertCount(2, $assign->testable_get_graders($student->id));
2033     }
2035     public function test_get_graders_separate_groups() {
2036         global $DB;
2038         $this->resetAfterTest();
2040         $course = $this->getDataGenerator()->create_course();
2041         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2042         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2043         $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2044         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2045         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2046         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2048         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2049         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2050         groups_add_member($group1, $student);
2052         $this->setAdminUser();
2054         // Force create an assignment with SEPARATEGROUPS.
2055         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2056         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2058         $assign = $this->create_instance($course, [
2059                 'groupingid' => $grouping->id,
2060                 'groupmode' => SEPARATEGROUPS,
2061             ]);
2063         $this->assertCount(4, $assign->testable_get_graders($student->id));
2065         // Note the second student is in a group that is not in the grouping.
2066         // This means that we get all graders that are not in a group in the grouping.
2067         $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2068     }
2070     public function test_get_notified_users() {
2071         global $CFG, $DB;
2073         $this->resetAfterTest();
2075         $course = $this->getDataGenerator()->create_course();
2076         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2077         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2078         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2080         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2081         groups_add_member($group1, $teacher);
2083         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2084         groups_add_member($group1, $editingteacher);
2086         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2087         groups_add_member($group1, $student);
2089         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2090         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2092         $capability = 'mod/assign:receivegradernotifications';
2093         $coursecontext = context_course::instance($course->id);
2094         $role = $DB->get_record('role', array('shortname' => 'teacher'));
2096         $this->setUser($teacher);
2098         // Create an assignment with no groups.
2099         $assign = $this->create_instance($course);
2101         $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2103         // Change nonediting teachers role to not receive grader notifications.
2104         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2106         // Only the editing teachers will be returned.
2107         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2109         // Note the second student is in a group that is not in the grouping.
2110         // This means that we get all graders that are not in a group in the grouping.
2111         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2112     }
2114     public function test_get_notified_users_in_grouping() {
2115         global $CFG, $DB;
2117         $this->resetAfterTest();
2119         $course = $this->getDataGenerator()->create_course();
2120         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2121         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2122         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2124         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2125         groups_add_member($group1, $teacher);
2127         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2128         groups_add_member($group1, $editingteacher);
2130         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2131         groups_add_member($group1, $student);
2133         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2134         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2136         // Force create an assignment with SEPARATEGROUPS.
2137         $assign = $this->create_instance($course, [
2138                 'groupingid' => $grouping->id,
2139                 'groupmode' => SEPARATEGROUPS,
2140             ]);
2142         // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2143         $this->setUser($student);
2144         $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2146         // Note the second student is in a group that is not in the grouping.
2147         // This means that we get all graders that are not in a group in the grouping.
2148         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2150         // Change nonediting teachers role to not receive grader notifications.
2151         $capability = 'mod/assign:receivegradernotifications';
2152         $coursecontext = context_course::instance($course->id);
2153         $role = $DB->get_record('role', ['shortname' => 'teacher']);
2154         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2156         // Only the editing teachers will be returned.
2157         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2159         // Note the second student is in a group that is not in the grouping.
2160         // This means that we get all graders that are not in a group in the grouping.
2161         // Unfortunately there are no editing teachers who are not in a group.
2162         $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2163     }
2165     public function test_group_members_only() {
2166         global $CFG;
2168         $this->resetAfterTest();
2170         $course = $this->getDataGenerator()->create_course();
2171         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2172         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2173         $this->getDataGenerator()->create_grouping_group([
2174                 'groupid' => $group1->id,
2175                 'groupingid' => $grouping->id,
2176             ]);
2178         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2179         $this->getDataGenerator()->create_grouping_group([
2180                 'groupid' => $group2->id,
2181                 'groupingid' => $grouping->id,
2182             ]);
2184         $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2186         // Add users in the following groups
2187         // - Teacher - Group 1.
2188         // - Student - Group 1.
2189         // - Student - Group 2.
2190         // - Student - Unrelated Group
2191         // - Student - No group.
2192         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2193         groups_add_member($group1, $teacher);
2195         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2196         groups_add_member($group1, $student);
2198         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2199         groups_add_member($group2, $otherstudent);
2201         $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2202         groups_add_member($group2, $otherstudent);
2204         $this->getDataGenerator()->create_and_enrol($course, 'student');
2206         $this->setAdminUser();
2208         $CFG->enableavailability = true;
2209         $assign = $this->create_instance($course, [], [
2210                 'availability' => json_encode(
2211                     \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2212                 ),
2213                 'groupingid' => $grouping->id,
2214             ]);
2216         // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2217         // group, or the student in an unrelated group.
2218         $this->setUser($teacher);
2219         $participants = $assign->list_participants(0, true);
2220         $this->assertCount(2, $participants);
2221         $this->assertTrue(isset($participants[$student->id]));
2222         $this->assertTrue(isset($participants[$otherstudent->id]));
2223     }
2225     public function test_get_uniqueid_for_user() {
2226         $this->resetAfterTest();
2228         $course = $this->getDataGenerator()->create_course();
2229         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2230         $students = [];
2231         for ($i = 0; $i < 10; $i++) {
2232             $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2233             $students[$student->id] = $student;
2234         }
2236         $this->setUser($teacher);
2237         $assign = $this->create_instance($course);
2239         foreach ($students as $student) {
2240             $uniqueid = $assign->get_uniqueid_for_user($student->id);
2241             $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2242         }
2243     }
2245     public function test_show_student_summary() {
2246         global $CFG, $PAGE;
2248         $this->resetAfterTest();
2250         $course = $this->getDataGenerator()->create_course();
2251         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2252         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2253         $this->setUser($teacher);
2254         $assign = $this->create_instance($course);
2255         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2257         // No feedback should be available because this student has not been graded.
2258         $this->setUser($student);
2259         $output = $assign->view_student_summary($student, true);
2260         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if there is no grade');
2262         // Simulate adding a grade.
2263         $this->add_submission($student, $assign);
2264         $this->submit_for_grading($student, $assign);
2265         $this->mark_submission($teacher, $assign, $student);
2267         // Now we should see the feedback.
2268         $this->setUser($student);
2269         $output = $assign->view_student_summary($student, true);
2270         $this->assertRegexp('/Feedback/', $output, 'Show feedback if there is a grade');
2272         // Now hide the grade in gradebook.
2273         $this->setUser($teacher);
2274         require_once($CFG->libdir.'/gradelib.php');
2275         $gradeitem = new grade_item(array(
2276             'itemtype'      => 'mod',
2277             'itemmodule'    => 'assign',
2278             'iteminstance'  => $assign->get_instance()->id,
2279             'courseid'      => $course->id));
2281         $gradeitem->set_hidden(1, false);
2283         // No feedback should be available because the grade is hidden.
2284         $this->setUser($student);
2285         $output = $assign->view_student_summary($student, true);
2286         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2287     }
2289     public function test_show_student_summary_with_feedback() {
2290         global $CFG, $PAGE;
2292         $this->resetAfterTest();
2294         $course = $this->getDataGenerator()->create_course();
2295         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2296         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2297         $this->setUser($teacher);
2298         $assign = $this->create_instance($course, [
2299                 'assignfeedback_comments_enabled' => 1
2300             ]);
2301         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2303         // No feedback should be available because this student has not been graded.
2304         $this->setUser($student);
2305         $output = $assign->view_student_summary($student, true);
2306         $this->assertNotRegexp('/Feedback/', $output);
2308         // Simulate adding a grade.
2309         $this->add_submission($student, $assign);
2310         $this->submit_for_grading($student, $assign);
2311         $this->mark_submission($teacher, $assign, $student, null, [
2312                 'assignfeedbackcomments_editor' => [
2313                     'text' => 'Tomato sauce',
2314                     'format' => FORMAT_MOODLE,
2315                 ],
2316             ]);
2318         // Should have feedback but no grade.
2319         $this->setUser($student);
2320         $output = $assign->view_student_summary($student, true);
2321         $this->assertRegexp('/Feedback/', $output);
2322         $this->assertRegexp('/Tomato sauce/', $output);
2323         $this->assertNotRegexp('/Grade/', $output, 'Do not show grade when there is no grade.');
2324         $this->assertNotRegexp('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2326         // Add a grade now.
2327         $this->mark_submission($teacher, $assign, $student, 50.0, [
2328                 'assignfeedbackcomments_editor' => [
2329                     'text' => 'Bechamel sauce',
2330                     'format' => FORMAT_MOODLE,
2331                 ],
2332             ]);
2334         // Should have feedback but no grade.
2335         $this->setUser($student);
2336         $output = $assign->view_student_summary($student, true);
2337         $this->assertNotRegexp('/Tomato sauce/', $output);
2338         $this->assertRegexp('/Bechamel sauce/', $output);
2339         $this->assertRegexp('/Grade/', $output);
2340         $this->assertRegexp('/Graded on/', $output);
2342         // Now hide the grade in gradebook.
2343         $this->setUser($teacher);
2344         $gradeitem = new grade_item(array(
2345             'itemtype'      => 'mod',
2346             'itemmodule'    => 'assign',
2347             'iteminstance'  => $assign->get_instance()->id,
2348             'courseid'      => $course->id));
2350         $gradeitem->set_hidden(1, false);
2352         // No feedback should be available because the grade is hidden.
2353         $this->setUser($student);
2354         $output = $assign->view_student_summary($student, true);
2355         $this->assertNotRegexp('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2356     }
2358     /**
2359      * Test reopen behavior when in "Manual" mode.
2360      */
2361     public function test_attempt_reopen_method_manual() {
2362         global $PAGE;
2364         $this->resetAfterTest();
2365         $course = $this->getDataGenerator()->create_course();
2366         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2367         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2369         $assign = $this->create_instance($course, [
2370                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2371                 'maxattempts' => 3,
2372                 'submissiondrafts' => 1,
2373                 'assignsubmission_onlinetext_enabled' => 1,
2374             ]);
2375         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2377         // Student should be able to see an add submission button.
2378         $this->setUser($student);
2379         $output = $assign->view_student_summary($student, true);
2380         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2382         // Add a submission.
2383         $this->add_submission($student, $assign);
2384         $this->submit_for_grading($student, $assign);
2386         // Verify the student cannot make changes to the submission.
2387         $output = $assign->view_student_summary($student, true);
2388         $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2390         // Mark the submission.
2391         $this->mark_submission($teacher, $assign, $student);
2393         // Check the student can see the grade.
2394         $this->setUser($student);
2395         $output = $assign->view_student_summary($student, true);
2396         $this->assertNotEquals(false, strpos($output, '50.0'));
2398         // Allow the student another attempt.
2399         $teacher->ignoresesskey = true;
2400         $this->setUser($teacher);
2401         $result = $assign->testable_process_add_attempt($student->id);
2402         $this->assertEquals(true, $result);
2404         // Check that the previous attempt is now in the submission history table.
2405         $this->setUser($student);
2406         $output = $assign->view_student_summary($student, true);
2407         // Need a better check.
2408         $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2410         // Check that the student now has a button for Add a new attempt".
2411         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2412         // Check that the student now does not have a button for Submit.
2413         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2415         // Check that the student now has a submission history.
2416         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2418         $this->setUser($teacher);
2419         // Check that the grading table loads correctly and contains this user.
2420         // This is also testing that we do not get duplicate rows in the grading table.
2421         $gradingtable = new assign_grading_table($assign, 100, '', 0, true);
2422         $output = $assign->get_renderer()->render($gradingtable);
2423         $this->assertEquals(true, strpos($output, $student->lastname));
2425         // Should be 1 not 2.
2426         $this->assertEquals(1, $assign->count_submissions());
2427         $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2428         $this->assertEquals(0, $assign->count_submissions_need_grading());
2429         $this->assertEquals(1, $assign->count_grades());
2431         // Change max attempts to unlimited.
2432         $formdata = clone($assign->get_instance());
2433         $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2434         $formdata->instance = $formdata->id;
2435         $assign->update_instance($formdata);
2437         // Mark the submission again.
2438         $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2440         // Check the grade exists.
2441         $this->setUser($teacher);
2442         $grades = $assign->get_user_grades_for_gradebook($student->id);
2443         $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2445         // Check we can reopen still.
2446         $result = $assign->testable_process_add_attempt($student->id);
2447         $this->assertEquals(true, $result);
2449         // Should no longer have a grade because there is no grade for the latest attempt.
2450         $grades = $assign->get_user_grades_for_gradebook($student->id);
2451         $this->assertEmpty($grades);
2452     }
2454     /**
2455      * Test reopen behavior when in "Reopen until pass" mode.
2456      */
2457     public function test_attempt_reopen_method_untilpass() {
2458         global $PAGE;
2460         $this->resetAfterTest();
2461         $course = $this->getDataGenerator()->create_course();
2462         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2463         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2465         $assign = $this->create_instance($course, [
2466                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2467                 'maxattempts' => 3,
2468                 'submissiondrafts' => 1,
2469                 'assignsubmission_onlinetext_enabled' => 1,
2470             ]);
2471         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2473         // Set grade to pass to 80.
2474         $gradeitem = $assign->get_grade_item();
2475         $gradeitem->gradepass = '80.0';
2476         $gradeitem->update();
2478         // Student should be able to see an add submission button.
2479         $this->setUser($student);
2480         $output = $assign->view_student_summary($student, true);
2481         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2483         // Add a submission.
2484         $this->add_submission($student, $assign);
2485         $this->submit_for_grading($student, $assign);
2487         // Verify the student cannot make a new attempt.
2488         $output = $assign->view_student_summary($student, true);
2489         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2491         // Mark the submission as non-passing.
2492         $this->mark_submission($teacher, $assign, $student, 50.0);
2494         // Check the student can see the grade.
2495         $this->setUser($student);
2496         $output = $assign->view_student_summary($student, true);
2497         $this->assertNotEquals(false, strpos($output, '50.0'));
2499         // Check that the student now has a button for Add a new attempt.
2500         $output = $assign->view_student_summary($student, true);
2501         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2503         // Check that the student now does not have a button for Submit.
2504         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2506         // Check that the student now has a submission history.
2507         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2509         // Add a second submission.
2510         $this->add_submission($student, $assign);
2511         $this->submit_for_grading($student, $assign);
2513         // Mark the submission as passing.
2514         $this->mark_submission($teacher, $assign, $student, 80.0);
2516         // Check that the student does not have a button for Add a new attempt.
2517         $this->setUser($student);
2518         $output = $assign->view_student_summary($student, true);
2519         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2521         // Re-mark the submission as not passing.
2522         $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2524         // Check that the student now has a button for Add a new attempt.
2525         $this->setUser($student);
2526         $output = $assign->view_student_summary($student, true);
2527         $this->assertRegexp('/' . get_string('addnewattempt', 'assign') . '/', $output);
2528         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2529     }
2531     public function test_attempt_reopen_method_untilpass_passing() {
2532         global $PAGE;
2534         $this->resetAfterTest();
2535         $course = $this->getDataGenerator()->create_course();
2536         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2537         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2539         $assign = $this->create_instance($course, [
2540                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2541                 'maxattempts' => 3,
2542                 'submissiondrafts' => 1,
2543                 'assignsubmission_onlinetext_enabled' => 1,
2544             ]);
2545         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2547         // Set grade to pass to 80.
2548         $gradeitem = $assign->get_grade_item();
2549         $gradeitem->gradepass = '80.0';
2550         $gradeitem->update();
2552         // Student should be able to see an add submission button.
2553         $this->setUser($student);
2554         $output = $assign->view_student_summary($student, true);
2555         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2557         // Add a submission as a student.
2558         $this->add_submission($student, $assign);
2559         $this->submit_for_grading($student, $assign);
2561         // Mark the submission as passing.
2562         $this->mark_submission($teacher, $assign, $student, 100.0);
2564         // Check the student can see the grade.
2565         $this->setUser($student);
2566         $output = $assign->view_student_summary($student, true);
2567         $this->assertNotEquals(false, strpos($output, '100.0'));
2569         // Check that the student does not have a button for Add a new attempt.
2570         $output = $assign->view_student_summary($student, true);
2571         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2572     }
2574     public function test_attempt_reopen_method_untilpass_no_passing_requirement() {
2575         global $PAGE;
2577         $this->resetAfterTest();
2578         $course = $this->getDataGenerator()->create_course();
2579         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2580         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2582         $assign = $this->create_instance($course, [
2583                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2584                 'maxattempts' => 3,
2585                 'submissiondrafts' => 1,
2586                 'assignsubmission_onlinetext_enabled' => 1,
2587             ]);
2588         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2590         // Set grade to pass to 0, so that no attempts should reopen.
2591         $gradeitem = $assign->get_grade_item();
2592         $gradeitem->gradepass = '0';
2593         $gradeitem->update();
2595         // Student should be able to see an add submission button.
2596         $this->setUser($student);
2597         $output = $assign->view_student_summary($student, true);
2598         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2600         // Add a submission.
2601         $this->add_submission($student, $assign);
2602         $this->submit_for_grading($student, $assign);
2604         // Mark the submission with any grade.
2605         $this->mark_submission($teacher, $assign, $student, 0.0);
2607         // Check the student can see the grade.
2608         $this->setUser($student);
2609         $output = $assign->view_student_summary($student, true);
2610         $this->assertNotEquals(false, strpos($output, '0.0'));
2612         // Check that the student does not have a button for Add a new attempt.
2613         $output = $assign->view_student_summary($student, true);
2614         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2615     }
2617     /**
2618      * Test student visibility for each stage of the marking workflow.
2619      */
2620     public function test_markingworkflow() {
2621         global $PAGE;
2623         $this->resetAfterTest();
2624         $course = $this->getDataGenerator()->create_course();
2625         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2626         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2628         $assign = $this->create_instance($course, [
2629                 'markingworkflow' => 1,
2630             ]);
2632         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2634         // Mark the submission and set to notmarked.
2635         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2636                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2637             ]);
2639         // Check the student can't see the grade.
2640         $this->setUser($student);
2641         $output = $assign->view_student_summary($student, true);
2642         $this->assertEquals(false, strpos($output, '50.0'));
2644         // Make sure the grade isn't pushed to the gradebook.
2645         $grades = $assign->get_user_grades_for_gradebook($student->id);
2646         $this->assertEmpty($grades);
2648         // Mark the submission and set to inmarking.
2649         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2650                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2651             ]);
2653         // Check the student can't see the grade.
2654         $this->setUser($student);
2655         $output = $assign->view_student_summary($student, true);
2656         $this->assertEquals(false, strpos($output, '50.0'));
2658         // Make sure the grade isn't pushed to the gradebook.
2659         $grades = $assign->get_user_grades_for_gradebook($student->id);
2660         $this->assertEmpty($grades);
2662         // Mark the submission and set to readyforreview.
2663         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2664                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2665             ]);
2667         // Check the student can't see the grade.
2668         $this->setUser($student);
2669         $output = $assign->view_student_summary($student, true);
2670         $this->assertEquals(false, strpos($output, '50.0'));
2672         // Make sure the grade isn't pushed to the gradebook.
2673         $grades = $assign->get_user_grades_for_gradebook($student->id);
2674         $this->assertEmpty($grades);
2676         // Mark the submission and set to inreview.
2677         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2678                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2679             ]);
2681         // Check the student can't see the grade.
2682         $this->setUser($student);
2683         $output = $assign->view_student_summary($student, true);
2684         $this->assertEquals(false, strpos($output, '50.0'));
2686         // Make sure the grade isn't pushed to the gradebook.
2687         $grades = $assign->get_user_grades_for_gradebook($student->id);
2688         $this->assertEmpty($grades);
2690         // Mark the submission and set to readyforrelease.
2691         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2692                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
2693             ]);
2695         // Check the student can't see the grade.
2696         $this->setUser($student);
2697         $output = $assign->view_student_summary($student, true);
2698         $this->assertEquals(false, strpos($output, '50.0'));
2700         // Make sure the grade isn't pushed to the gradebook.
2701         $grades = $assign->get_user_grades_for_gradebook($student->id);
2702         $this->assertEmpty($grades);
2704         // Mark the submission and set to released.
2705         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2706                 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
2707             ]);
2709         // Check the student can see the grade.
2710         $this->setUser($student);
2711         $output = $assign->view_student_summary($student, true);
2712         $this->assertNotEquals(false, strpos($output, '50.0'));
2714         // Make sure the grade is pushed to the gradebook.
2715         $grades = $assign->get_user_grades_for_gradebook($student->id);
2716         $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
2717     }
2719     /**
2720      * Test that a student allocated a specific marker is only shown to that marker.
2721      */
2722     public function test_markerallocation() {
2723         global $PAGE;
2725         $this->resetAfterTest();
2726         $course = $this->getDataGenerator()->create_course();
2727         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2728         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2729         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2731         $assign = $this->create_instance($course, [
2732                 'markingworkflow' => 1,
2733                 'markingallocation' => 1
2734             ]);
2736         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2738         // Allocate marker to submission.
2739         $this->mark_submission($teacher, $assign, $student, null, [
2740             'allocatedmarker' => $teacher->id,
2741         ]);
2743         // Check the allocated marker can view the submission.
2744         $this->setUser($teacher);
2745         $users = $assign->list_participants(0, true);
2746         $this->assertEquals(1, count($users));
2747         $this->assertTrue(isset($users[$student->id]));
2749         $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2750         $context = context_module::instance($cm->id);
2751         $assign = new testable_assign($context, $cm, $course);
2753         // Check that other teachers can't view this submission.
2754         $this->setUser($otherteacher);
2755         $users = $assign->list_participants(0, true);
2756         $this->assertEquals(0, count($users));
2757     }
2759     /**
2760      * Ensure that a teacher cannot submit for students as standard.
2761      */
2762     public function test_teacher_submit_for_student() {
2763         global $PAGE;
2765         $this->resetAfterTest();
2766         $course = $this->getDataGenerator()->create_course();
2767         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2768         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2770         $assign = $this->create_instance($course, [
2771             'assignsubmission_onlinetext_enabled' => 1,
2772             'submissiondrafts' => 1,
2773         ]);
2775         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2777         // Add a submission but do not submit.
2778         $this->add_submission($student, $assign, 'Student submission text');
2780         $this->setUser($student);
2781         $output = $assign->view_student_summary($student, true);
2782         $this->assertContains('Student submission text', $output, 'Contains student submission text');
2784         // Check that a teacher can not edit the submission as they do not have the capability.
2785         $this->setUser($teacher);
2786         $this->expectException('moodle_exception');
2787         $this->expectExceptionMessage('error/nopermission');
2788         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2789     }
2791     /**
2792      * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
2793      */
2794     public function test_teacher_submit_for_student_with_capability() {
2795         global $PAGE;
2797         $this->resetAfterTest();
2798         $course = $this->getDataGenerator()->create_course();
2799         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2800         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2801         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2803         $assign = $this->create_instance($course, [
2804             'assignsubmission_onlinetext_enabled' => 1,
2805             'submissiondrafts' => 1,
2806         ]);
2808         // Add the required capability.
2809         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2810         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2811         role_assign($roleid, $teacher->id, $assign->get_context()->id);
2812         accesslib_clear_all_caches_for_unit_testing();
2814         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2816         // Add a submission but do not submit.
2817         $this->add_submission($student, $assign, 'Student submission text');
2819         $this->setUser($student);
2820         $output = $assign->view_student_summary($student, true);
2821         $this->assertContains('Student submission text', $output, 'Contains student submission text');
2823         // Check that a teacher can edit the submission.
2824         $this->setUser($teacher);
2825         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2827         $this->setUser($student);
2828         $output = $assign->view_student_summary($student, true);
2829         $this->assertNotContains('Student submission text', $output, 'Contains student submission text');
2830         $this->assertContains('Teacher edited submission text', $output, 'Contains teacher edited submission text');
2832         // Check that the teacher can submit the students work.
2833         $this->setUser($teacher);
2834         $this->submit_for_grading($student, $assign, [], false);
2836         // Revert to draft so the student can edit it.
2837         $assign->revert_to_draft($student->id);
2839         $this->setUser($student);
2841         // Check that the submission text was saved.
2842         $output = $assign->view_student_summary($student, true);
2843         $this->assertContains('Teacher edited submission text', $output, 'Contains student submission text');
2845         // Check that the student can submit their work.
2846         $this->submit_for_grading($student, $assign, []);
2848         $output = $assign->view_student_summary($student, true);
2849         $this->assertNotContains(get_string('addsubmission', 'assign'), $output);
2851         // An editing teacher without the extra role should still be able to revert to draft.
2852         $this->setUser($otherteacher);
2854         // Revert to draft so the submission is editable.
2855         $assign->revert_to_draft($student->id);
2856     }
2858     /**
2859      * Ensure that disabling submit after the cutoff date works as expected.
2860      */
2861     public function test_disable_submit_after_cutoff_date() {
2862         global $PAGE;
2864         $this->resetAfterTest();
2865         $course = $this->getDataGenerator()->create_course();
2866         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2868         $now = time();
2869         $tomorrow = $now + DAYSECS;
2870         $lastweek = $now - (7 * DAYSECS);
2871         $yesterday = $now - DAYSECS;
2873         $this->setAdminUser();
2874         $assign = $this->create_instance($course, [
2875                 'duedate' => $yesterday,
2876                 'cutoffdate' => $tomorrow,
2877                 'assignsubmission_onlinetext_enabled' => 1,
2878             ]);
2880         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2882         // Student should be able to see an add submission button.
2883         $this->setUser($student);
2884         $output = $assign->view_student_summary($student, true);
2885         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2887         // Add a submission but don't submit now.
2888         $this->add_submission($student, $assign);
2890         // Create another instance with cut-off and due-date already passed.
2891         $this->setAdminUser();
2892         $assign = $this->create_instance($course, [
2893                 'duedate' => $lastweek,
2894                 'cutoffdate' => $yesterday,
2895                 'assignsubmission_onlinetext_enabled' => 1,
2896             ]);
2898         $this->setUser($student);
2899         $output = $assign->view_student_summary($student, true);
2900         $this->assertNotContains($output, get_string('editsubmission', 'assign'),
2901                                  'Should not be able to edit after cutoff date.');
2902         $this->assertNotContains($output, get_string('submitassignment', 'assign'),
2903                                  'Should not be able to submit after cutoff date.');
2904     }
2906     /**
2907      * Testing for submission comment plugin settings.
2908      *
2909      * @dataProvider submission_plugin_settings_provider
2910      * @param   bool    $globalenabled
2911      * @param   array   $instanceconfig
2912      * @param   bool    $isenabled
2913      */
2914     public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled) {
2915         global $CFG;
2917         $this->resetAfterTest();
2918         $course = $this->getDataGenerator()->create_course();
2920         $CFG->usecomments = $globalenabled;
2921         $assign = $this->create_instance($course, $instanceconfig);
2922         $plugin = $assign->get_submission_plugin_by_type('comments');
2923         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
2924     }
2926     public function submission_plugin_settings_provider() {
2927         return [
2928             'CFG->usecomments true, empty config => Enabled by default' => [
2929                 true,
2930                 [],
2931                 true,
2932             ],
2933             'CFG->usecomments true, config enabled => Comments enabled' => [
2934                 true,
2935                 [
2936                     'assignsubmission_comments_enabled' => 1,
2937                 ],
2938                 true,
2939             ],
2940             'CFG->usecomments true, config idisabled => Comments enabled' => [
2941                 true,
2942                 [
2943                     'assignsubmission_comments_enabled' => 0,
2944                 ],
2945                 true,
2946             ],
2947             'CFG->usecomments false, empty config => Disabled by default' => [
2948                 false,
2949                 [],
2950                 false,
2951             ],
2952             'CFG->usecomments false, config enabled => Comments disabled' => [
2953                 false,
2954                 [
2955                     'assignsubmission_comments_enabled' => 1,
2956                 ],
2957                 false,
2958             ],
2959             'CFG->usecomments false, config disabled => Comments disabled' => [
2960                 false,
2961                 [
2962                     'assignsubmission_comments_enabled' => 0,
2963                 ],
2964                 false,
2965             ],
2966         ];
2967     }
2969     /**
2970      * Testing for comment inline settings
2971      */
2972     public function test_feedback_comment_commentinline() {
2973         global $CFG;
2975         $this->resetAfterTest();
2976         $course = $this->getDataGenerator()->create_course();
2977         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2978         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2980         $sourcetext = "Hello!
2982 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
2984 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
2985 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
2987 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
2988 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
2989 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
2990 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
2991 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
2992 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
2993 ";
2995         // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
2996         $filteredtext = "Hello!
2998 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3000 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3001 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3003 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3004 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3005 Internal link 1:
3006 Internal link 2:
3007 Anchor link 1:Link text
3008 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3009 ";
3011         $this->setUser($teacher);
3012         $assign = $this->create_instance($course, [
3013                 'assignsubmission_onlinetext_enabled' => 1,
3014                 'assignfeedback_comments_enabled' => 1,
3015                 'assignfeedback_comments_commentinline' => 1,
3016             ]);
3018         $this->setUser($student);
3020         // Add a submission but don't submit now.
3021         $this->add_submission($student, $assign, $sourcetext);
3023         $this->setUser($teacher);
3025         $data = new stdClass();
3026         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3027         $pagination = [
3028                 'userid' => $student->id,
3029                 'rownum' => 0,
3030                 'last' => true,
3031                 'useridlistid' => $assign->get_useridlist_key_id(),
3032                 'attemptnumber' => 0,
3033             ];
3034         $formparams = array($assign, $data, $pagination);
3035         $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
3037         $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3038     }
3040     /**
3041      * Testing for feedback comment plugin settings.
3042      *
3043      * @dataProvider feedback_plugin_settings_provider
3044      * @param   array   $instanceconfig
3045      * @param   bool    $isenabled
3046      */
3047     public function test_feedback_plugin_settings($instanceconfig, $isenabled) {
3048         $this->resetAfterTest();
3049         $course = $this->getDataGenerator()->create_course();
3051         $assign = $this->create_instance($course, $instanceconfig);
3052         $plugin = $assign->get_feedback_plugin_by_type('comments');
3053         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3054     }
3056     public function feedback_plugin_settings_provider() {
3057         return [
3058             'No configuration => disabled' => [
3059                 [],
3060                 false,
3061             ],
3062             'Actively disabled' => [
3063                 [
3064                     'assignfeedback_comments_enabled' => 0,
3065                 ],
3066                 false,
3067             ],
3068             'Actively enabled' => [
3069                 [
3070                     'assignfeedback_comments_enabled' => 1,
3071                 ],
3072                 true,
3073             ],
3074         ];
3075     }
3077     /**
3078      * Testing if gradebook feedback plugin is enabled.
3079      */
3080     public function test_is_gradebook_feedback_enabled() {
3081         $this->resetAfterTest();
3082         $course = $this->getDataGenerator()->create_course();
3083         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3084         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3086         $adminconfig = get_config('assign');
3087         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3089         // Create assignment with gradebook feedback enabled and grade = 0.
3090         $assign = $this->create_instance($course, [
3091                 "{$gradebookplugin}_enabled" => 1,
3092                 'grades' => 0,
3093             ]);
3095         // Get gradebook feedback plugin.
3096         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3097         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3098         $this->assertEquals(1, $plugin->is_enabled('enabled'));
3099         $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3100     }
3102     /**
3103      * Testing if gradebook feedback plugin is disabled.
3104      */
3105     public function test_is_gradebook_feedback_disabled() {
3106         $this->resetAfterTest();
3107         $course = $this->getDataGenerator()->create_course();
3108         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3109         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3111         $adminconfig = get_config('assign');
3112         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3114         // Create assignment with gradebook feedback disabled and grade = 0.
3115         $assign = $this->create_instance($course, [
3116                 "{$gradebookplugin}_enabled" => 0,
3117                 'grades' => 0,
3118             ]);
3120         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3121         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3122         $this->assertEquals(0, $plugin->is_enabled('enabled'));
3123     }
3125     /**
3126      * Testing can_edit_submission.
3127      */
3128     public function test_can_edit_submission() {
3129         $this->resetAfterTest();
3130         $course = $this->getDataGenerator()->create_course();
3131         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3132         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3133         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3135         $assign = $this->create_instance($course, [
3136                 'assignsubmission_onlinetext_enabled' => 1,
3137                 'submissiondrafts' => 1,
3138             ]);
3140         // Check student can edit their own submission.
3141         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3143         // Check student cannot edit others submission.
3144         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3146         // Check teacher cannot (by default) edit a students submission.
3147         $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3148     }
3150     /**
3151      * Testing can_edit_submission with the editothersubmission capability.
3152      */
3153     public function test_can_edit_submission_with_editothersubmission() {
3154         $this->resetAfterTest();
3155         $course = $this->getDataGenerator()->create_course();
3156         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3157         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3158         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3160         $assign = $this->create_instance($course, [
3161                 'assignsubmission_onlinetext_enabled' => 1,
3162                 'submissiondrafts' => 1,
3163             ]);
3165         // Add the required capability to edit a student submission.
3166         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3167         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3168         role_assign($roleid, $teacher->id, $assign->get_context()->id);
3169         accesslib_clear_all_caches_for_unit_testing();
3171         // Check student can edit their own submission.
3172         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3174         // Check student cannot edit others submission.
3175         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3177         // Retest - should now have access.
3178         $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3179     }
3181     /**
3182      * Testing can_edit_submission
3183      */
3184     public function test_can_edit_submission_separategroups() {
3185         $this->resetAfterTest();
3186         $course = $this->getDataGenerator()->create_course();
3187         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3189         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3190         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3191         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3192         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3194         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3195         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3196         groups_assign_grouping($grouping->id, $group1->id);
3197         groups_add_member($group1, $student1);
3198         groups_add_member($group1, $student2);
3200         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3201         groups_assign_grouping($grouping->id, $group2->id);
3202         groups_add_member($group2, $student3);
3203         groups_add_member($group2, $student4);
3205         $assign = $this->create_instance($course, [
3206                 'assignsubmission_onlinetext_enabled' => 1,
3207                 'submissiondrafts' => 1,
3208                 'groupingid' => $grouping->id,
3209                 'groupmode' => SEPARATEGROUPS,
3210             ]);
3212         // Verify a student does not have the ability to edit submissions for other users.
3213         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3214         $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id));
3215         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3216         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3217     }
3219     /**
3220      * Testing can_edit_submission
3221      */
3222     public function test_can_edit_submission_separategroups_with_editothersubmission() {
3223         $this->resetAfterTest();
3224         $course = $this->getDataGenerator()->create_course();
3225         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3227         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3228         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3229         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3230         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3232         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3233         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3234         groups_assign_grouping($grouping->id, $group1->id);
3235         groups_add_member($group1, $student1);
3236         groups_add_member($group1, $student2);
3238         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3239         groups_assign_grouping($grouping->id, $group2->id);
3240         groups_add_member($group2, $student3);
3241         groups_add_member($group2, $student4);
3243         $assign = $this->create_instance($course, [
3244                 'assignsubmission_onlinetext_enabled' => 1,
3245                 'submissiondrafts' => 1,
3246                 'groupingid' => $grouping->id,
3247                 'groupmode' => SEPARATEGROUPS,
3248             ]);
3250         // Add the capability to the new assignment for student 1.
3251         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3252         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3253         role_assign($roleid, $student1->id, $assign->get_context()->id);
3254         accesslib_clear_all_caches_for_unit_testing();
3256         // Verify student1 has the ability to edit submissions for other users in their group, but not other groups.
3257         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3258         $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id));
3259         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3260         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3262         // Verify other students do not have the ability to edit submissions for other users.
3263         $this->assertTrue($assign->can_edit_submission($student2->id, $student2->id));
3264         $this->assertFalse($assign->can_edit_submission($student1->id, $student2->id));
3265         $this->assertFalse($assign->can_edit_submission($student3->id, $student2->id));
3266         $this->assertFalse($assign->can_edit_submission($student4->id, $student2->id));
3267     }
3269     /**
3270      * Test if the view blind details capability works
3271      */
3272     public function test_can_view_blind_details() {
3273         global $DB;
3275         $this->resetAfterTest();
3276         $course = $this->getDataGenerator()->create_course();
3277         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3278         $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
3279         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3281         $assign = $this->create_instance($course, [
3282                 'blindmarking' => 1,
3283             ]);
3285         $this->assertTrue($assign->is_blind_marking());
3287         // Test student names are hidden to teacher.
3288         $this->setUser($teacher);
3289         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
3290         $output = $assign->get_renderer()->render($gradingtable);
3291         $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));    // "Participant" is somewhere on the page.
3292         $this->assertEquals(false, strpos($output, fullname($student)));    // Students full name doesn't appear.
3294         // Test student names are visible to manager.
3295         $this->setUser($manager);
3296         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
3297         $output = $assign->get_renderer()->render($gradingtable);
3298         $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
3299         $this->assertEquals(true, strpos($output, fullname($student)));
3300     }
3302     /**
3303      * Testing get_shared_group_members
3304      */
3305     public function test_get_shared_group_members() {
3306         $this->resetAfterTest();
3307         $course = $this->getDataGenerator()->create_course();
3308         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3310         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3311         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3312         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3313         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student