Merge branch 'dynreg-upgrade-squash' of https://github.com/cengage/moodle
[moodle.git] / mod / assign / tests / locallib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Unit tests for (some of) mod/assign/locallib.php.
19  *
20  * @package    mod_assign
21  * @category   phpunit
22  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
30 require_once($CFG->dirroot . '/mod/assign/locallib.php');
31 require_once($CFG->dirroot . '/mod/assign/upgradelib.php');
32 require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
34 /**
35  * Unit tests for (some of) mod/assign/locallib.php.
36  *
37  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
38  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class mod_assign_locallib_testcase extends advanced_testcase {
42     // Use the generator helper.
43     use mod_assign_test_generator;
45     public function test_return_links() {
46         global $PAGE;
48         $this->resetAfterTest();
49         $course = $this->getDataGenerator()->create_course();
51         $assign = $this->create_instance($course);
52         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
54         $assign->register_return_link('RETURNACTION', ['param' => 1]);
55         $this->assertEquals('RETURNACTION', $assign->get_return_action());
56         $this->assertEquals(['param' => 1], $assign->get_return_params());
57     }
59     public function test_get_feedback_plugins() {
60         $this->resetAfterTest();
61         $course = $this->getDataGenerator()->create_course();
62         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
64         $this->setUser($teacher);
65         $assign = $this->create_instance($course);
66         $installedplugins = array_keys(core_component::get_plugin_list('assignfeedback'));
68         foreach ($assign->get_feedback_plugins() as $plugin) {
69             $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins');
70         }
71     }
73     public function test_get_submission_plugins() {
74         $this->resetAfterTest();
75         $course = $this->getDataGenerator()->create_course();
76         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
78         $this->setUser($teacher);
79         $assign = $this->create_instance($course);
80         $installedplugins = array_keys(core_component::get_plugin_list('assignsubmission'));
82         foreach ($assign->get_submission_plugins() as $plugin) {
83             $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins');
84         }
85     }
87     public function test_is_blind_marking() {
88         $this->resetAfterTest();
89         $course = $this->getDataGenerator()->create_course();
90         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
91         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
93         $this->setUser($teacher);
94         $assign = $this->create_instance($course, ['blindmarking' => 1]);
95         $this->assertEquals(true, $assign->is_blind_marking());
97         // Test cannot see student names.
98         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
99         $output = $assign->get_renderer()->render($gradingtable);
100         $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
102         // Test students cannot reveal identities.
103         $nopermission = false;
104         $student->ignoresesskey = true;
105         $this->setUser($student);
106         $this->expectException('required_capability_exception');
107         $assign->reveal_identities();
108         $student->ignoresesskey = false;
110         // Test teachers cannot reveal identities.
111         $nopermission = false;
112         $teacher->ignoresesskey = true;
113         $this->setUser($teacher);
114         $this->expectException('required_capability_exception');
115         $assign->reveal_identities();
116         $teacher->ignoresesskey = false;
118         // Test sesskey is required.
119         $this->setUser($teacher);
120         $this->expectException('moodle_exception');
121         $assign->reveal_identities();
123         // Test editingteacher can reveal identities if sesskey is ignored.
124         $teacher->ignoresesskey = true;
125         $this->setUser($teacher);
126         $assign->reveal_identities();
127         $this->assertEquals(false, $assign->is_blind_marking());
128         $teacher->ignoresesskey = false;
130         // Test student names are visible.
131         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
132         $output = $assign->get_renderer()->render($gradingtable);
133         $this->assertEquals(false, strpos($output, get_string('hiddenuser', 'assign')));
135         // Set this back to default.
136         $teacher->ignoresesskey = false;
137     }
139     /**
140      * Data provider for test_get_assign_perpage
141      *
142      * @return array Provider data
143      */
144     public function get_assign_perpage_provider() {
145         return array(
146             array(
147                 'maxperpage' => -1,
148                 'userprefs' => array(
149                     -1 => -1,
150                     10 => 10,
151                     20 => 20,
152                     50 => 50,
153                 ),
154             ),
155             array(
156                 'maxperpage' => 15,
157                 'userprefs' => array(
158                     -1 => 15,
159                     10 => 10,
160                     20 => 15,
161                     50 => 15,
162                 ),
163             ),
164         );
165     }
167     /**
168      * Test maxperpage
169      *
170      * @dataProvider get_assign_perpage_provider
171      * @param integer $maxperpage site config value
172      * @param array $userprefs Array of user preferences and expected page sizes
173      */
174     public function test_get_assign_perpage($maxperpage, $userprefs) {
175         $this->resetAfterTest();
176         $course = $this->getDataGenerator()->create_course();
177         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
178         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
180         $this->setUser($teacher);
181         $assign = $this->create_instance($course);
183         set_config('maxperpage', $maxperpage, 'assign');
184         set_user_preference('assign_perpage', null);
185         $this->assertEquals(10, $assign->get_assign_perpage());
186         foreach ($userprefs as $pref => $perpage) {
187             set_user_preference('assign_perpage', $pref);
188             $this->assertEquals($perpage, $assign->get_assign_perpage());
189         }
190     }
192     /**
193      * Test filter by requires grading.
194      *
195      * This is specifically checking an assignment with no grade to make sure we do not
196      * get an exception thrown when rendering the grading table for this type of assignment.
197      */
198     public function test_gradingtable_filter_by_requiresgrading_no_grade() {
199         global $PAGE;
201         $this->resetAfterTest();
203         $course = $this->getDataGenerator()->create_course();
204         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
205         $this->setUser($teacher);
206         $assign = $this->create_instance($course, [
207                 'assignsubmission_onlinetext_enabled' => 1,
208                 'assignfeedback_comments_enabled' => 0,
209                 'grade' => GRADE_TYPE_NONE
210             ]);
212         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
213             'id' => $assign->get_course_module()->id,
214             'action' => 'grading',
215         )));
217         // Render the table with the requires grading filter.
218         $gradingtable = new assign_grading_table($assign, 1, ASSIGN_FILTER_REQUIRE_GRADING, 0, true);
219         $output = $assign->get_renderer()->render($gradingtable);
221         // Test that the filter function does not throw errors for assignments with no grade.
222         $this->assertStringContainsString(get_string('nothingtodisplay'), $output);
223     }
226     /**
227      * Test submissions with extension date.
228      */
229     public function test_gradingtable_extension_due_date() {
230         global $PAGE;
232         $this->resetAfterTest();
233         $course = $this->getDataGenerator()->create_course();
234         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
235         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
237         // Setup the assignment.
238         $this->setUser($teacher);
239         $time = time();
240         $assign = $this->create_instance($course, [
241                 'assignsubmission_onlinetext_enabled' => 1,
242                 'duedate' => time() - (4 * DAYSECS),
243             ]);
244         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
245             'id' => $assign->get_course_module()->id,
246             'action' => 'grading',
247         )));
249         // Check that the assignment is late.
250         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
251         $output = $assign->get_renderer()->render($gradingtable);
252         $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
253         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS))), $output);
255         // Grant an extension.
256         $extendedtime = $time + (2 * DAYSECS);
257         $assign->testable_save_user_extension($student->id, $extendedtime);
258         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
259         $output = $assign->get_renderer()->render($gradingtable);
260         $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
261         $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
263         // Simulate a submission.
264         $this->setUser($student);
265         $submission = $assign->get_user_submission($student->id, true);
266         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
267         $assign->testable_update_submission($submission, $student->id, true, false);
268         $data = new stdClass();
269         $data->onlinetext_editor = [
270             'itemid' => file_get_unused_draft_itemid(),
271             'text' => 'Submission text',
272             'format' => FORMAT_MOODLE,
273         ];
274         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
275         $plugin->save($submission, $data);
277         // Verify output.
278         $this->setUser($teacher);
279         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
280         $output = $assign->get_renderer()->render($gradingtable);
281         $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
282         $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
283     }
285     /**
286      * Test that late submissions with extension date calculate correctly.
287      */
288     public function test_gradingtable_extension_date_calculation_for_lateness() {
289         global $PAGE;
291         $this->resetAfterTest();
292         $course = $this->getDataGenerator()->create_course();
293         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
294         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
296         // Setup the assignment.
297         $this->setUser($teacher);
298         $time = time();
299         $assign = $this->create_instance($course, [
300                 'assignsubmission_onlinetext_enabled' => 1,
301                 'duedate' => time() - (4 * DAYSECS),
302             ]);
303         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
304             'id' => $assign->get_course_module()->id,
305             'action' => 'grading',
306         )));
308         // Check that the assignment is late.
309         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
310         $output = $assign->get_renderer()->render($gradingtable);
311         $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
312         $difftime = time() - $time;
313         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
315         // Grant an extension that is in the past.
316         $assign->testable_save_user_extension($student->id, $time - (2 * DAYSECS));
317         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
318         $output = $assign->get_renderer()->render($gradingtable);
319         $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
320         $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
321         $difftime = time() - $time;
322         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
324         // Simulate a submission.
325         $this->setUser($student);
326         $submission = $assign->get_user_submission($student->id, true);
327         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
328         $assign->testable_update_submission($submission, $student->id, true, false);
329         $data = new stdClass();
330         $data->onlinetext_editor = [
331             'itemid' => file_get_unused_draft_itemid(),
332             'text' => 'Submission text',
333             'format' => FORMAT_MOODLE,
334         ];
335         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
336         $plugin->save($submission, $data);
337         $submittedtime = time();
339         // Verify output.
340         $this->setUser($teacher);
341         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
342         $output = $assign->get_renderer()->render($gradingtable);
343         $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
344         $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
346         $difftime = $submittedtime - $time;
347         $this->assertStringContainsString(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)),
348             $output);
349     }
351     public function test_gradingtable_status_rendering() {
352         global $PAGE;
354         $this->resetAfterTest();
355         $course = $this->getDataGenerator()->create_course();
356         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
357         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
359         // Setup the assignment.
360         $this->setUser($teacher);
361         $time = time();
362         $assign = $this->create_instance($course, [
363             'assignsubmission_onlinetext_enabled' => 1,
364             'duedate' => $time - (4 * DAYSECS),
365          ]);
366         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
367             'id' => $assign->get_course_module()->id,
368             'action' => 'grading',
369         )));
371         // Check that the assignment is late.
372         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
373         $output = $assign->get_renderer()->render($gradingtable);
374         $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
375         $difftime = time() - $time;
376         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
378         // Simulate a student viewing the assignment without submitting.
379         $this->setUser($student);
380         $submission = $assign->get_user_submission($student->id, true);
381         $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
382         $assign->testable_update_submission($submission, $student->id, true, false);
383         $submittedtime = time();
385         // Verify output.
386         $this->setUser($teacher);
387         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
388         $output = $assign->get_renderer()->render($gradingtable);
389         $difftime = $submittedtime - $time;
390         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
392         $document = new DOMDocument();
393         @$document->loadHTML($output);
394         $xpath = new DOMXPath($document);
395         $this->assertEmpty($xpath->evaluate('string(//td[@id="mod_assign_grading-' . $assign->get_context()->id. '_r0_c8"])'));
396     }
398     /**
399      * Check that group submission information is rendered correctly in the
400      * grading table.
401      */
402     public function test_gradingtable_group_submissions_rendering() {
403         global $PAGE;
405         $this->resetAfterTest();
406         $course = $this->getDataGenerator()->create_course();
407         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
409         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
410         groups_add_member($group, $teacher);
412         $students = [];
414         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
415         $students[] = $student;
416         groups_add_member($group, $student);
418         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
419         $students[] = $student;
420         groups_add_member($group, $student);
422         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
423         $students[] = $student;
424         groups_add_member($group, $student);
426         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
427         $students[] = $student;
428         groups_add_member($group, $student);
430         // Verify group assignments.
431         $this->setUser($teacher);
432         $assign = $this->create_instance($course, [
433             'teamsubmission' => 1,
434             'assignsubmission_onlinetext_enabled' => 1,
435             'submissiondrafts' => 1,
436             'requireallteammemberssubmit' => 0,
437         ]);
438         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
439             'id' => $assign->get_course_module()->id,
440             'action' => 'grading',
441         )));
443         // Add a submission.
444         $this->setUser($student);
445         $data = new stdClass();
446         $data->onlinetext_editor = [
447             'itemid' => file_get_unused_draft_itemid(),
448             'text' => 'Submission text',
449             'format' => FORMAT_MOODLE,
450         ];
451         $notices = array();
452         $assign->save_submission($data, $notices);
454         $submission = $assign->get_group_submission($student->id, 0, true);
455         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
456         $assign->testable_update_submission($submission, $student->id, true, true);
458         // Check output.
459         $this->setUser($teacher);
460         $gradingtable = new assign_grading_table($assign, 4, '', 0, true);
461         $output = $assign->get_renderer()->render($gradingtable);
462         $document = new DOMDocument();
463         @$document->loadHTML($output);
464         $xpath = new DOMXPath($document);
466         // The XPath expression is based on the unique ID of the table.
467         $xpathuniqueidroot = 'mod_assign_grading-' . $assign->get_context()->id;
469         // Check status.
470         $this->assertSame(get_string('submissionstatus_submitted', 'assign'),
471             $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c4"]/div[@class="submissionstatussubmitted"])'));
472         $this->assertSame(get_string('submissionstatus_submitted', 'assign'),
473             $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c4"]/div[@class="submissionstatussubmitted"])'));
475         // Check submission last modified date.
476         $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c8"])')));
477         $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c8"])')));
479         // Check group.
480         $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c5"])'));
481         $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c5"])'));
483         // Check submission text.
484         $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c9"]/div/div)'));
485         $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c9"]/div/div)'));
487         // Check comments can be made.
488         $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r0_c10"]//textarea)'));
489         $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r3_c10"]//textarea)'));
490     }
492     public function test_show_intro() {
493         $this->resetAfterTest();
494         $course = $this->getDataGenerator()->create_course();
495         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
497         // Test whether we are showing the intro at the correct times.
498         $this->setUser($teacher);
499         $assign = $this->create_instance($course, ['alwaysshowdescription' => 1]);
501         $this->assertEquals(true, $assign->testable_show_intro());
503         $tomorrow = time() + DAYSECS;
505         $assign = $this->create_instance($course, [
506                 'alwaysshowdescription' => 0,
507                 'allowsubmissionsfromdate' => $tomorrow,
508             ]);
509         $this->assertEquals(false, $assign->testable_show_intro());
510         $yesterday = time() - DAYSECS;
511         $assign = $this->create_instance($course, [
512                 'alwaysshowdescription' => 0,
513                 'allowsubmissionsfromdate' => $yesterday,
514             ]);
515         $this->assertEquals(true, $assign->testable_show_intro());
516     }
518     public function test_has_submissions_or_grades() {
519         $this->resetAfterTest();
520         $course = $this->getDataGenerator()->create_course();
521         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
522         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
524         $this->setUser($teacher);
525         $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
526         $instance = $assign->get_instance();
528         // Should start empty.
529         $this->assertEquals(false, $assign->has_submissions_or_grades());
531         // Simulate a submission.
532         $this->setUser($student);
533         $submission = $assign->get_user_submission($student->id, true);
535         // The submission is still new.
536         $this->assertEquals(false, $assign->has_submissions_or_grades());
538         // Submit the submission.
539         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
540         $assign->testable_update_submission($submission, $student->id, true, false);
541         $data = new stdClass();
542         $data->onlinetext_editor = array(
543             'itemid' => file_get_unused_draft_itemid(),
544             'text' => 'Submission text',
545             'format' => FORMAT_MOODLE);
546         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
547         $plugin->save($submission, $data);
549         // Now test again.
550         $this->assertEquals(true, $assign->has_submissions_or_grades());
551     }
553     public function test_delete_grades() {
554         $this->resetAfterTest();
555         $course = $this->getDataGenerator()->create_course();
556         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
557         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
559         $this->setUser($teacher);
560         $assign = $this->create_instance($course);
562         // Simulate adding a grade.
563         $this->setUser($teacher);
564         $data = new stdClass();
565         $data->grade = '50.0';
566         $assign->testable_apply_grade_to_user($data, $student->id, 0);
568         // Now see if the data is in the gradebook.
569         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
571         $this->assertNotEquals(0, count($gradinginfo->items));
573         $assign->testable_delete_grades();
574         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
576         $this->assertEquals(0, count($gradinginfo->items));
577     }
579     public function test_delete_instance() {
580         $this->resetAfterTest();
581         $course = $this->getDataGenerator()->create_course();
582         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
583         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
585         $this->setUser($teacher);
586         $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
588         // Simulate adding a grade.
589         $this->setUser($teacher);
590         $data = new stdClass();
591         $data->grade = '50.0';
592         $assign->testable_apply_grade_to_user($data, $student->id, 0);
594         // Simulate a submission.
595         $this->add_submission($student, $assign);
597         // Now try and delete.
598         $this->setUser($teacher);
599         $this->assertEquals(true, $assign->delete_instance());
600     }
602     public function test_reset_userdata() {
603         global $DB;
605         $this->resetAfterTest();
606         $course = $this->getDataGenerator()->create_course();
607         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
608         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
610         $now = time();
611         $this->setUser($teacher);
612         $assign = $this->create_instance($course, [
613                 'assignsubmission_onlinetext_enabled' => 1,
614                 'duedate' => $now,
615             ]);
617         // Simulate adding a grade.
618         $this->add_submission($student, $assign);
619         $this->submit_for_grading($student, $assign);
620         $this->mark_submission($teacher, $assign, $student, 50.0);
622         // Simulate a submission.
623         $this->setUser($student);
624         $submission = $assign->get_user_submission($student->id, true);
625         $data = new stdClass();
626         $data->onlinetext_editor = array(
627             'itemid' => file_get_unused_draft_itemid(),
628             'text' => 'Submission text',
629             'format' => FORMAT_MOODLE);
630         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
631         $plugin->save($submission, $data);
633         $this->assertEquals(true, $assign->has_submissions_or_grades());
634         // Now try and reset.
635         $data = new stdClass();
636         $data->reset_assign_submissions = 1;
637         $data->reset_gradebook_grades = 1;
638         $data->reset_assign_user_overrides = 1;
639         $data->reset_assign_group_overrides = 1;
640         $data->courseid = $course->id;
641         $data->timeshift = DAYSECS;
642         $this->setUser($teacher);
643         $assign->reset_userdata($data);
644         $this->assertEquals(false, $assign->has_submissions_or_grades());
646         // Reload the instance data.
647         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
648         $this->assertEquals($now + DAYSECS, $instance->duedate);
650         // Test reset using assign_reset_userdata().
651         $assignduedate = $instance->duedate; // Keep old updated value for comparison.
652         $data->timeshift = (2 * DAYSECS);
653         assign_reset_userdata($data);
654         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
655         $this->assertEquals($assignduedate + (2 * DAYSECS), $instance->duedate);
657         // Create one more assignment and reset, make sure time shifted for previous assignment is not changed.
658         $assign2 = $this->create_instance($course, [
659                 'assignsubmission_onlinetext_enabled' => 1,
660                 'duedate' => $now,
661             ]);
662         $assignduedate = $instance->duedate;
663         $data->timeshift = 3 * DAYSECS;
664         $assign2->reset_userdata($data);
665         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
666         $this->assertEquals($assignduedate, $instance->duedate);
667         $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
668         $this->assertEquals($now + 3 * DAYSECS, $instance2->duedate);
670         // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date.
671         $assignduedate = $instance->duedate;
672         $assign2duedate = $instance2->duedate;
673         $data->timeshift = (4 * DAYSECS);
674         assign_reset_userdata($data);
675         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
676         $this->assertEquals($assignduedate + (4 * DAYSECS), $instance->duedate);
677         $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
678         $this->assertEquals($assign2duedate + (4 * DAYSECS), $instance2->duedate);
679     }
681     public function test_plugin_settings() {
682         global $DB;
684         $this->resetAfterTest();
686         $course = $this->getDataGenerator()->create_course();
687         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
689         $now = time();
690         $this->setUser($teacher);
691         $assign = $this->create_instance($course, [
692                 'assignsubmission_file_enabled' => 1,
693                 'assignsubmission_file_maxfiles' => 12,
694                 'assignsubmission_file_maxsizebytes' => 10,
695             ]);
697         $plugin = $assign->get_submission_plugin_by_type('file');
698         $this->assertEquals('12', $plugin->get_config('maxfilesubmissions'));
699     }
701     public function test_update_calendar() {
702         global $DB;
704         $this->resetAfterTest();
706         $course = $this->getDataGenerator()->create_course();
707         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
709         $this->setUser($teacher);
710         $userctx = context_user::instance($teacher->id)->id;
712         // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey.
713         $draftid = file_get_unused_draft_itemid();
714         $_REQUEST['introeditor'] = $draftid;
715         $_POST['introeditor'] = $draftid;
716         $_POST['sesskey'] = sesskey();
718         // Write links to a draft area.
719         $fakearealink1 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">link</a>', 'draftfile.php', $userctx,
720             'user', 'draft', $draftid);
721         $fakearealink2 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">new</a>', 'draftfile.php', $userctx,
722             'user', 'draft', $draftid);
724         // Create a new assignment with links to a draft area.
725         $now = time();
726         $assign = $this->create_instance($course, [
727                 'duedate' => $now,
728                 'intro' => $fakearealink1,
729                 'introformat' => FORMAT_HTML
730             ]);
732         // See if there is an event in the calendar.
733         $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
734         $event = $DB->get_record('event', $params);
735         $this->assertNotEmpty($event);
736         $this->assertSame('link', $event->description);     // The pluginfile links are removed.
738         // Make sure the same works when updating the assignment.
739         $instance = $assign->get_instance();
740         $instance->instance = $instance->id;
741         $instance->intro = $fakearealink2;
742         $instance->introformat = FORMAT_HTML;
743         $assign->update_instance($instance);
744         $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
745         $event = $DB->get_record('event', $params);
746         $this->assertNotEmpty($event);
747         $this->assertSame('new', $event->description);     // The pluginfile links are removed.
749         // Create an assignment with a description that should be hidden.
750         $assign = $this->create_instance($course, [
751                 'duedate' => $now + 160,
752                 'alwaysshowdescription' => false,
753                 'allowsubmissionsfromdate' => $now + 60,
754                 'intro' => 'Some text',
755             ]);
757         // Get the event from the calendar.
758         $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
759         $event = $DB->get_record('event', [
760             'modulename' => 'assign',
761             'instance' => $assign->get_instance()->id,
762         ]);
764         $this->assertEmpty($event->description);
766         // Change the allowsubmissionfromdate to the past - do this directly in the DB
767         // because if we call the assignment update method - it will update the calendar
768         // and we want to test that this works from cron.
769         $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id' => $assign->get_instance()->id));
770         // Run cron to update the event in the calendar.
771         assign::cron();
772         $event = $DB->get_record('event', $params);
774         $this->assertStringContainsString('Some text', $event->description);
776     }
778     public function test_update_instance() {
779         global $DB;
781         $this->resetAfterTest();
783         $course = $this->getDataGenerator()->create_course();
784         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
786         $this->setUser($teacher);
787         $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
789         $now = time();
790         $instance = $assign->get_instance();
791         $instance->duedate = $now;
792         $instance->instance = $instance->id;
793         $instance->assignsubmission_onlinetext_enabled = 1;
795         $assign->update_instance($instance);
797         $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
798         $this->assertEquals($now, $instance->duedate);
799     }
801     public function test_cannot_submit_empty() {
802         global $PAGE;
804         $this->resetAfterTest();
806         $course = $this->getDataGenerator()->create_course();
807         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
809         $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
811         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
813         // Test you cannot see the submit button for an offline assignment regardless.
814         $this->setUser($student);
815         $output = $assign->view_student_summary($student, true);
816         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'),
817             $output, 'Can submit empty offline assignment');
818     }
820     public function test_cannot_submit_empty_no_submission() {
821         global $PAGE;
823         $this->resetAfterTest();
825         $course = $this->getDataGenerator()->create_course();
826         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
828         $assign = $this->create_instance($course, [
829             'submissiondrafts' => 1,
830             'assignsubmission_onlinetext_enabled' => 1,
831         ]);
833         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
835         // Test you cannot see the submit button for an online text assignment with no submission.
836         $this->setUser($student);
837         $output = $assign->view_student_summary($student, true);
838         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'),
839             $output, 'Cannot submit empty onlinetext assignment');
840     }
842     public function test_can_submit_with_submission() {
843         global $PAGE;
845         $this->resetAfterTest();
847         $course = $this->getDataGenerator()->create_course();
848         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
850         $assign = $this->create_instance($course, [
851             'submissiondrafts' => 1,
852             'assignsubmission_onlinetext_enabled' => 1,
853         ]);
855         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
857         // Add a draft.
858         $this->add_submission($student, $assign);
860         // Test you can see the submit button for an online text assignment with a submission.
861         $this->setUser($student);
862         $output = $assign->view_student_summary($student, true);
863         $this->assertStringContainsString(get_string('submitassignment', 'assign'),
864             $output, 'Can submit non empty onlinetext assignment');
865     }
867     /**
868      * Test new_submission_empty
869      *
870      * We only test combinations of plugins here. Individual plugins are tested
871      * in their respective test files.
872      *
873      * @dataProvider test_new_submission_empty_testcases
874      * @param string $data The file submission data
875      * @param bool $expected The expected return value
876      */
877     public function test_new_submission_empty($data, $expected) {
878         $this->resetAfterTest();
880         $course = $this->getDataGenerator()->create_course();
881         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
883         $assign = $this->create_instance($course, [
884                 'assignsubmission_file_enabled' => 1,
885                 'assignsubmission_file_maxfiles' => 12,
886                 'assignsubmission_file_maxsizebytes' => 10,
887                 'assignsubmission_onlinetext_enabled' => 1,
888             ]);
889         $this->setUser($student);
890         $submission = new stdClass();
892         if ($data['file'] && isset($data['file']['filename'])) {
893             $itemid = file_get_unused_draft_itemid();
894             $submission->files_filemanager = $itemid;
895             $data['file'] += ['contextid' => context_user::instance($student->id)->id, 'itemid' => $itemid];
896             $fs = get_file_storage();
897             $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']);
898         }
900         if ($data['onlinetext']) {
901             $submission->onlinetext_editor = ['text' => $data['onlinetext']];
902         }
904         $result = $assign->new_submission_empty($submission);
905         $this->assertTrue($result === $expected);
906     }
908     /**
909      * Dataprovider for the test_new_submission_empty testcase
910      *
911      * @return array of testcases
912      */
913     public function test_new_submission_empty_testcases() {
914         return [
915             'With file and onlinetext' => [
916                 [
917                     'file' => [
918                         'component' => 'user',
919                         'filearea' => 'draft',
920                         'filepath' => '/',
921                         'filename' => 'not_a_virus.exe'
922                     ],
923                     'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu'
924                 ],
925                 false
926             ]
927         ];
928     }
930     public function test_list_participants() {
931         global $CFG;
933         $this->resetAfterTest();
935         $course = $this->getDataGenerator()->create_course();
936         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
938         // Create 10 students.
939         for ($i = 0; $i < 10; $i++) {
940             $this->getDataGenerator()->create_and_enrol($course, 'student');
941         }
943         $this->setUser($teacher);
944         $assign = $this->create_instance($course, ['grade' => 100]);
946         $this->assertCount(10, $assign->list_participants(null, true));
947     }
949     public function test_list_participants_activeenrol() {
950         global $CFG, $DB;
952         $this->resetAfterTest();
954         $course = $this->getDataGenerator()->create_course();
955         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
957         // Create 10 students.
958         for ($i = 0; $i < 10; $i++) {
959             $this->getDataGenerator()->create_and_enrol($course, 'student');
960         }
962         // Create 10 suspended students.
963         for ($i = 0; $i < 10; $i++) {
964             $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
965         }
967         $this->setUser($teacher);
968         set_user_preference('grade_report_showonlyactiveenrol', false);
969         $assign = $this->create_instance($course, ['grade' => 100]);
971         $this->assertCount(10, $assign->list_participants(null, true));
972     }
974     public function test_list_participants_with_group_restriction() {
975         global $CFG;
977         $this->resetAfterTest();
979         $course = $this->getDataGenerator()->create_course();
980         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
981         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
982         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
983         $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
985         // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group.
986         $CFG->enableavailability = true;
988         $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
989         $assign = $this->create_instance($course, [
990             'grade' => 100,
991             'availability' => json_encode(
992                 \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)])
993             ),
994         ]);
996         groups_add_member($specialgroup, $student);
997         groups_add_member($specialgroup, $otherstudent);
998         $this->assertEquals(2, count($assign->list_participants(null, true)));
999     }
1001     public function test_get_participant_user_not_exist() {
1002         $this->resetAfterTest();
1003         $course = $this->getDataGenerator()->create_course();
1005         $assign = $this->create_instance($course);
1006         $this->assertNull($assign->get_participant('-1'));
1007     }
1009     public function test_get_participant_not_enrolled() {
1010         $this->resetAfterTest();
1011         $course = $this->getDataGenerator()->create_course();
1012         $assign = $this->create_instance($course);
1014         $user = $this->getDataGenerator()->create_user();
1015         $this->assertNull($assign->get_participant($user->id));
1016     }
1018     public function test_get_participant_no_submission() {
1019         $this->resetAfterTest();
1020         $course = $this->getDataGenerator()->create_course();
1021         $assign = $this->create_instance($course);
1022         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1024         $participant = $assign->get_participant($student->id);
1026         $this->assertEquals($student->id, $participant->id);
1027         $this->assertFalse($participant->submitted);
1028         $this->assertFalse($participant->requiregrading);
1029         $this->assertFalse($participant->grantedextension);
1030     }
1032     public function test_get_participant_granted_extension() {
1033         $this->resetAfterTest();
1034         $course = $this->getDataGenerator()->create_course();
1035         $assign = $this->create_instance($course);
1036         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1037         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1039         $this->setUser($teacher);
1040         $assign->save_user_extension($student->id, time());
1041         $participant = $assign->get_participant($student->id);
1043         $this->assertEquals($student->id, $participant->id);
1044         $this->assertFalse($participant->submitted);
1045         $this->assertFalse($participant->requiregrading);
1046         $this->assertTrue($participant->grantedextension);
1047     }
1049     public function test_get_participant_with_ungraded_submission() {
1050         $this->resetAfterTest();
1051         $course = $this->getDataGenerator()->create_course();
1052         $assign = $this->create_instance($course);
1053         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1054         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1056         // Simulate a submission.
1057         $this->add_submission($student, $assign);
1058         $this->submit_for_grading($student, $assign);
1060         $participant = $assign->get_participant($student->id);
1062         $this->assertEquals($student->id, $participant->id);
1063         $this->assertTrue($participant->submitted);
1064         $this->assertTrue($participant->requiregrading);
1065         $this->assertFalse($participant->grantedextension);
1066     }
1068     public function test_get_participant_with_graded_submission() {
1069         $this->resetAfterTest();
1070         $course = $this->getDataGenerator()->create_course();
1071         $assign = $this->create_instance($course);
1072         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1073         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1075         // Simulate a submission.
1076         $this->add_submission($student, $assign);
1077         $this->submit_for_grading($student, $assign);
1079         $this->mark_submission($teacher, $assign, $student, 50.0);
1081         $data = new stdClass();
1082         $data->grade = '50.0';
1083         $assign->testable_apply_grade_to_user($data, $student->id, 0);
1085         $participant = $assign->get_participant($student->id);
1087         $this->assertEquals($student->id, $participant->id);
1088         $this->assertTrue($participant->submitted);
1089         $this->assertFalse($participant->requiregrading);
1090         $this->assertFalse($participant->grantedextension);
1091     }
1093     /**
1094      * No active group and non-group submissions disallowed => 2 groups.
1095      */
1096     public function test_count_teams_no_active_non_group_allowed() {
1097         $this->resetAfterTest();
1099         $course = $this->getDataGenerator()->create_course();
1100         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1101         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1102         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1104         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1105         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1106         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1107         groups_add_member($group1, $student1);
1108         groups_add_member($group2, $student2);
1110         $this->setUser($teacher);
1111         $assign = $this->create_instance($course, ['teamsubmission' => 1]);
1113         $this->assertEquals(2, $assign->count_teams());
1114     }
1116     /**
1117      * No active group and non group submissions allowed => 2 groups + the default one.
1118      */
1119     public function test_count_teams_non_group_allowed() {
1120         $this->resetAfterTest();
1122         $course = $this->getDataGenerator()->create_course();
1123         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1124         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1125         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1126         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1128         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1129         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1131         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1132         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1133         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1135         groups_add_member($group1, $student1);
1136         groups_add_member($group2, $student2);
1138         $assign = $this->create_instance($course, [
1139             'teamsubmission' => 1,
1140             'teamsubmissiongroupingid' => $grouping->id,
1141             'preventsubmissionnotingroup' => false,
1142         ]);
1144         $this->setUser($teacher);
1145         $this->assertEquals(3, $assign->count_teams());
1147         // Active group only.
1148         $this->assertEquals(1, $assign->count_teams($group1->id));
1149         $this->assertEquals(1, $assign->count_teams($group2->id));
1150     }
1152     /**
1153      * Active group => just selected one.
1154      */
1155     public function test_count_teams_no_active_group() {
1156         $this->resetAfterTest();
1158         $course = $this->getDataGenerator()->create_course();
1159         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1160         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1161         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1162         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1164         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1165         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1167         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1168         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1169         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1171         groups_add_member($group1, $student1);
1172         groups_add_member($group2, $student2);
1174         $assign = $this->create_instance($course, [
1175             'teamsubmission' => 1,
1176             'preventsubmissionnotingroup' => true,
1177         ]);
1179         $this->setUser($teacher);
1180         $this->assertEquals(2, $assign->count_teams());
1182         // Active group only.
1183         $this->assertEquals(1, $assign->count_teams($group1->id));
1184         $this->assertEquals(1, $assign->count_teams($group2->id));
1185     }
1187     /**
1188      * Active group => just selected one.
1189      */
1190     public function test_count_teams_groups_only() {
1191         $this->resetAfterTest();
1193         $course = $this->getDataGenerator()->create_course();
1194         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1196         $assign = $this->create_instance($course, [
1197             'teamsubmission' => 1,
1198             'teamsubmissiongroupingid' => $grouping->id,
1199             'preventsubmissionnotingroup' => false,
1200         ]);
1201         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1203         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1204         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1205         groups_add_member($group1, $student1);
1207         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1208         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1209         groups_add_member($group2, $student2);
1211         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1212         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1214         $this->setUser($teacher);
1216         $assign = $this->create_instance($course, [
1217             'teamsubmission' => 1,
1218             'preventsubmissionnotingroup' => true,
1219         ]);
1220         $this->assertEquals(2, $assign->count_teams());
1221     }
1223     public function test_submit_to_default_group() {
1224         global $DB, $SESSION;
1226         $this->resetAfterTest();
1228         $course = $this->getDataGenerator()->create_course();
1229         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1230         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1232         $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1233         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1235         $assign = $this->create_instance($course, [
1236             'teamsubmission' => 1,
1237             'assignsubmission_onlinetext_enabled' => 1,
1238             'submissiondrafts' => 0,
1239             'groupmode' => VISIBLEGROUPS,
1240         ]);
1242         $usergroup = $assign->get_submission_group($student->id);
1243         $this->assertFalse($usergroup, 'New student is in default group');
1245         // Add a submission.
1246         $this->add_submission($student, $assign);
1247         $this->submit_for_grading($student, $assign);
1249         // Set active groups to all groups.
1250         $this->setUser($teacher);
1251         $SESSION->activegroup[$course->id]['aag'][0] = 0;
1252         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1254         // Set an active group.
1255         $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id;
1256         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1257     }
1259     public function test_count_submissions_no_draft() {
1260         $this->resetAfterTest();
1262         $course = $this->getDataGenerator()->create_course();
1263         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1264         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1266         $assign = $this->create_instance($course, [
1267             'assignsubmission_onlinetext_enabled' => 1,
1268         ]);
1270         $assign->get_user_submission($student->id, true);
1272         // Note: Drafts count as a submission.
1273         $this->assertEquals(0, $assign->count_grades());
1274         $this->assertEquals(0, $assign->count_submissions());
1275         $this->assertEquals(1, $assign->count_submissions(true));
1276         $this->assertEquals(0, $assign->count_submissions_need_grading());
1277         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1278         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1279         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1280         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1281     }
1283     public function test_count_submissions_draft() {
1284         $this->resetAfterTest();
1286         $course = $this->getDataGenerator()->create_course();
1287         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1288         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1290         $assign = $this->create_instance($course, [
1291             'assignsubmission_onlinetext_enabled' => 1,
1292         ]);
1294         $this->add_submission($student, $assign);
1296         // Note: Drafts count as a submission.
1297         $this->assertEquals(0, $assign->count_grades());
1298         $this->assertEquals(1, $assign->count_submissions());
1299         $this->assertEquals(1, $assign->count_submissions(true));
1300         $this->assertEquals(0, $assign->count_submissions_need_grading());
1301         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1302         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1303         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1304         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1305     }
1307     public function test_count_submissions_submitted() {
1308         global $SESSION;
1310         $this->resetAfterTest();
1312         $course = $this->getDataGenerator()->create_course();
1313         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1314         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1316         $assign = $this->create_instance($course, [
1317             'assignsubmission_onlinetext_enabled' => 1,
1318         ]);
1320         $this->add_submission($student, $assign);
1321         $this->submit_for_grading($student, $assign);
1323         $this->assertEquals(0, $assign->count_grades());
1324         $this->assertEquals(1, $assign->count_submissions());
1325         $this->assertEquals(1, $assign->count_submissions(true));
1326         $this->assertEquals(1, $assign->count_submissions_need_grading());
1327         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1328         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1329         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1330         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1331     }
1333     public function test_count_submissions_graded() {
1334         $this->resetAfterTest();
1336         $course = $this->getDataGenerator()->create_course();
1337         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1338         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1340         $assign = $this->create_instance($course, [
1341             'assignsubmission_onlinetext_enabled' => 1,
1342         ]);
1344         $this->add_submission($student, $assign);
1345         $this->submit_for_grading($student, $assign);
1346         $this->mark_submission($teacher, $assign, $student, 50.0);
1348         // Although it has been graded, it is still marked as submitted.
1349         $this->assertEquals(1, $assign->count_grades());
1350         $this->assertEquals(1, $assign->count_submissions());
1351         $this->assertEquals(1, $assign->count_submissions(true));
1352         $this->assertEquals(0, $assign->count_submissions_need_grading());
1353         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1354         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1355         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1356         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1357     }
1359     public function test_count_submissions_graded_group() {
1360         global $SESSION;
1362         $this->resetAfterTest();
1364         $course = $this->getDataGenerator()->create_course();
1365         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1366         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1367         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1368         $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1369         groups_add_member($group, $student);
1371         $assign = $this->create_instance($course, [
1372             'assignsubmission_onlinetext_enabled' => 1,
1373             'groupmode' => VISIBLEGROUPS,
1374         ]);
1376         $this->add_submission($student, $assign);
1377         $this->submit_for_grading($student, $assign);
1379         // The user should still be listed when fetching all groups.
1380         $this->setUser($teacher);
1381         $SESSION->activegroup[$course->id]['aag'][0] = 0;
1382         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1384         // The user should still be listed when fetching just their group.
1385         $SESSION->activegroup[$course->id]['aag'][0] = $group->id;
1386         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1388         // The user should still be listed when fetching just their group.
1389         $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id;
1390         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1391     }
1393     // TODO
1394     public function x_test_count_submissions_for_team() {
1395         $this->resetAfterTest();
1397         $course = $this->getDataGenerator()->create_course();
1398         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1399         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1400         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1401         $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1402         groups_add_member($group, $student);
1404         $assign = $this->create_instance($course, [
1405             'assignsubmission_onlinetext_enabled' => 1,
1406             'teamsubmission' => 1,
1407         ]);
1409         // Add a graded submission.
1410         $this->add_submission($student, $assign);
1412         // Simulate adding a grade.
1413         $this->setUser($teacher);
1414         $data = new stdClass();
1415         $data->grade = '50.0';
1416         $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
1418         // Simulate a submission.
1419         $this->setUser($this->extrastudents[1]);
1420         $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
1421         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1422         $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1423         $data = new stdClass();
1424         $data->onlinetext_editor = array(
1425             'itemid' => file_get_unused_draft_itemid(),
1426             'text' => 'Submission text',
1427             'format' => FORMAT_MOODLE);
1428         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1429         $plugin->save($submission, $data);
1431         // Simulate a submission.
1432         $this->setUser($this->extrastudents[2]);
1433         $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
1434         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1435         $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1436         $data = new stdClass();
1437         $data->onlinetext_editor = array(
1438             'itemid' => file_get_unused_draft_itemid(),
1439             'text' => 'Submission text',
1440             'format' => FORMAT_MOODLE);
1441         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1442         $plugin->save($submission, $data);
1444         // Simulate a submission.
1445         $this->setUser($this->extrastudents[3]);
1446         $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
1447         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1448         $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1449         $data = new stdClass();
1450         $data->onlinetext_editor = array(
1451             'itemid' => file_get_unused_draft_itemid(),
1452             'text' => 'Submission text',
1453             'format' => FORMAT_MOODLE);
1454         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1455         $plugin->save($submission, $data);
1457         // Simulate adding a grade.
1458         $this->setUser($teacher);
1459         $data = new stdClass();
1460         $data->grade = '50.0';
1461         $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1462         $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1464         // Create a new submission with status NEW.
1465         $this->setUser($this->extrastudents[4]);
1466         $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
1468         $this->assertEquals(2, $assign->count_grades());
1469         $this->assertEquals(4, $assign->count_submissions());
1470         $this->assertEquals(5, $assign->count_submissions(true));
1471         $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1472         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1473     }
1475     public function test_get_grading_userid_list_only_active() {
1476         $this->resetAfterTest();
1478         $course = $this->getDataGenerator()->create_course();
1479         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1480         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1481         $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1482             $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1484         $this->setUser($teacher);
1486         $assign = $this->create_instance($course);
1487         $this->assertCount(1, $assign->testable_get_grading_userid_list());
1488     }
1490     public function test_get_grading_userid_list_all() {
1491         $this->resetAfterTest();
1493         $course = $this->getDataGenerator()->create_course();
1494         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1495         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1496         $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1497             $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1499         $this->setUser($teacher);
1500         set_user_preference('grade_report_showonlyactiveenrol', false);
1502         $assign = $this->create_instance($course);
1503         $this->assertCount(2, $assign->testable_get_grading_userid_list());
1504     }
1506     public function test_cron() {
1507         global $PAGE;
1508         $this->resetAfterTest();
1510         // First run cron so there are no messages waiting to be sent (from other tests).
1511         cron_setup_user();
1512         assign::cron();
1514         $course = $this->getDataGenerator()->create_course();
1515         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1516         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1518         // Now create an assignment and add some feedback.
1519         $this->setUser($teacher);
1520         $assign = $this->create_instance($course, [
1521             'sendstudentnotifications' => 1,
1522         ]);
1524         $this->add_submission($student, $assign);
1525         $this->submit_for_grading($student, $assign);
1526         $this->mark_submission($teacher, $assign, $student, 50.0);
1528         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1529         cron_setup_user();
1530         $sink = $this->redirectMessages();
1531         assign::cron();
1532         $messages = $sink->get_messages();
1534         $this->assertEquals(1, count($messages));
1535         $this->assertEquals(1, $messages[0]->notification);
1536         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1537         // Test customdata.
1538         $customdata = json_decode($messages[0]->customdata);
1539         $this->assertEquals($assign->get_course_module()->id, $customdata->cmid);
1540         $this->assertEquals($assign->get_instance()->id, $customdata->instance);
1541         $this->assertEquals('feedbackavailable', $customdata->messagetype);
1542         $userpicture = new user_picture($teacher);
1543         $userpicture->size = 1; // Use f1 size.
1544         $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
1545         $this->assertEquals(0, $customdata->uniqueidforuser);   // Not used in this case.
1546         $this->assertFalse($customdata->blindmarking);
1547     }
1549     public function test_cron_without_notifications() {
1550         $this->resetAfterTest();
1552         // First run cron so there are no messages waiting to be sent (from other tests).
1553         cron_setup_user();
1554         assign::cron();
1556         $course = $this->getDataGenerator()->create_course();
1557         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1558         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1560         // Now create an assignment and add some feedback.
1561         $this->setUser($teacher);
1562         $assign = $this->create_instance($course, [
1563             'sendstudentnotifications' => 1,
1564         ]);
1566         $this->add_submission($student, $assign);
1567         $this->submit_for_grading($student, $assign);
1568         $this->mark_submission($teacher, $assign, $student, 50.0, [
1569             'sendstudentnotifications' => 0,
1570         ]);
1572         cron_setup_user();
1573         $sink = $this->redirectMessages();
1574         assign::cron();
1575         $messages = $sink->get_messages();
1577         $this->assertEquals(0, count($messages));
1578     }
1580     public function test_cron_regraded() {
1581         $this->resetAfterTest();
1583         // First run cron so there are no messages waiting to be sent (from other tests).
1584         cron_setup_user();
1585         assign::cron();
1587         $course = $this->getDataGenerator()->create_course();
1588         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1589         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1591         // Now create an assignment and add some feedback.
1592         $this->setUser($teacher);
1593         $assign = $this->create_instance($course, [
1594             'sendstudentnotifications' => 1,
1595         ]);
1597         $this->add_submission($student, $assign);
1598         $this->submit_for_grading($student, $assign);
1599         $this->mark_submission($teacher, $assign, $student, 50.0);
1601         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1602         cron_setup_user();
1603         assign::cron();
1605         // Regrade.
1606         $this->mark_submission($teacher, $assign, $student, 50.0);
1608         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1609         cron_setup_user();
1610         $sink = $this->redirectMessages();
1611         assign::cron();
1612         $messages = $sink->get_messages();
1614         $this->assertEquals(1, count($messages));
1615         $this->assertEquals(1, $messages[0]->notification);
1616         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1617     }
1619     /**
1620      * Test delivery of grade notifications as controlled by marking workflow.
1621      */
1622     public function test_markingworkflow_cron() {
1623         $this->resetAfterTest();
1625         // First run cron so there are no messages waiting to be sent (from other tests).
1626         cron_setup_user();
1627         assign::cron();
1629         $course = $this->getDataGenerator()->create_course();
1630         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1631         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1633         // Now create an assignment and add some feedback.
1634         $this->setUser($teacher);
1635         $assign = $this->create_instance($course, [
1636             'sendstudentnotifications' => 1,
1637             'markingworkflow' => 1,
1638         ]);
1640         // Mark a submission but set the workflowstate to an unreleased state.
1641         // This should not trigger a notification.
1642         $this->add_submission($student, $assign);
1643         $this->submit_for_grading($student, $assign);
1644         $this->mark_submission($teacher, $assign, $student, 50.0, [
1645             'sendstudentnotifications' => 1,
1646             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1647         ]);
1649         cron_setup_user();
1650         $sink = $this->redirectMessages();
1651         assign::cron();
1652         $messages = $sink->get_messages();
1654         $this->assertEquals(0, count($messages));
1656         // Transition to the released state.
1657         $this->setUser($teacher);
1658         $submission = $assign->get_user_submission($student->id, true);
1659         $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1660         $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1662         // Now run cron and see that one message was sent.
1663         cron_setup_user();
1664         $sink = $this->redirectMessages();
1665         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1666         assign::cron();
1667         $messages = $sink->get_messages();
1669         $this->assertEquals(1, count($messages));
1670         $this->assertEquals(1, $messages[0]->notification);
1671         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1672     }
1674     public function test_cron_message_includes_courseid() {
1675         $this->resetAfterTest();
1677         // First run cron so there are no messages waiting to be sent (from other tests).
1678         cron_setup_user();
1679         assign::cron();
1681         $course = $this->getDataGenerator()->create_course();
1682         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1683         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1685         // Now create an assignment and add some feedback.
1686         $this->setUser($teacher);
1687         $assign = $this->create_instance($course, [
1688             'sendstudentnotifications' => 1,
1689         ]);
1691         // Mark a submission but set the workflowstate to an unreleased state.
1692         // This should not trigger a notification.
1693         $this->add_submission($student, $assign);
1694         $this->submit_for_grading($student, $assign);
1695         $this->mark_submission($teacher, $assign, $student);
1696         phpunit_util::stop_message_redirection();
1698         // Now run cron and see that one message was sent.
1699         cron_setup_user();
1700         $this->preventResetByRollback();
1701         $sink = $this->redirectEvents();
1702         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1703         assign::cron();
1705         $events = $sink->get_events();
1706         $event = reset($events);
1707         $this->assertInstanceOf('\core\event\notification_sent', $event);
1708         $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1709         $sink->close();
1710     }
1712     public function test_is_graded() {
1713         $this->resetAfterTest();
1715         $course = $this->getDataGenerator()->create_course();
1716         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1717         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1718         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1720         $assign = $this->create_instance($course);
1722         $this->add_submission($student, $assign);
1723         $this->submit_for_grading($student, $assign);
1724         $this->mark_submission($teacher, $assign, $student, 50.0);
1726         $this->setUser($teacher);
1727         $this->assertEquals(true, $assign->testable_is_graded($student->id));
1728         $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1729     }
1731     public function test_can_grade() {
1732         global $DB;
1734         $this->resetAfterTest();
1736         $course = $this->getDataGenerator()->create_course();
1737         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1738         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1740         $assign = $this->create_instance($course);
1742         $this->setUser($student);
1743         $this->assertEquals(false, $assign->can_grade());
1745         $this->setUser($teacher);
1746         $this->assertEquals(true, $assign->can_grade());
1748         // Test the viewgrades capability for other users.
1749         $this->setUser();
1750         $this->assertTrue($assign->can_grade($teacher->id));
1751         $this->assertFalse($assign->can_grade($student->id));
1753         // Test the viewgrades capability - without mod/assign:grade.
1754         $this->setUser($student);
1756         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1757         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1758         $this->assertEquals(false, $assign->can_grade());
1759     }
1761     public function test_can_view_submission() {
1762         global $DB;
1764         $this->resetAfterTest();
1766         $course = $this->getDataGenerator()->create_course();
1767         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1768         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1769         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1770         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1771         $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1772             $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1774         $assign = $this->create_instance($course);
1776         $this->setUser($student);
1777         $this->assertEquals(true, $assign->can_view_submission($student->id));
1778         $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1779         $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1781         $this->setUser($teacher);
1782         $this->assertEquals(true, $assign->can_view_submission($student->id));
1783         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1784         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1785         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1787         $this->setUser($editingteacher);
1788         $this->assertEquals(true, $assign->can_view_submission($student->id));
1789         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1790         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1791         $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1793         // Test the viewgrades capability - without mod/assign:grade.
1794         $this->setUser($student);
1795         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1796         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1797         $this->assertEquals(true, $assign->can_view_submission($student->id));
1798         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1799         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1800         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1801     }
1803     public function test_update_submission() {
1804         $this->resetAfterTest();
1806         $course = $this->getDataGenerator()->create_course();
1807         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1808         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1810         $assign = $this->create_instance($course);
1812         $this->add_submission($student, $assign);
1813         $submission = $assign->get_user_submission($student->id, 0);
1814         $assign->testable_update_submission($submission, $student->id, true, true);
1816         $this->setUser($teacher);
1818         // Verify the gradebook update.
1819         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1821         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1822         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1823     }
1825     public function test_update_submission_team() {
1826         $this->resetAfterTest();
1828         $course = $this->getDataGenerator()->create_course();
1829         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1830         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1832         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1833         groups_add_member($group, $student);
1835         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1836         groups_add_member($group, $otherstudent);
1838         $assign = $this->create_instance($course, [
1839             'teamsubmission' => 1,
1840         ]);
1842         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1843         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1844         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1846         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1847         $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1848         $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1850         $this->add_submission($student, $assign);
1851         $submission = $assign->get_group_submission($student->id, 0, true);
1852         $assign->testable_update_submission($submission, $student->id, true, true);
1854         // Verify the gradebook update for the student.
1855         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1857         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1858         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1860         // Verify the gradebook update for the other student.
1861         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1863         $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1864         $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1865     }
1867     public function test_update_submission_suspended() {
1868         $this->resetAfterTest();
1870         $course = $this->getDataGenerator()->create_course();
1871         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1872         $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1874         $assign = $this->create_instance($course);
1876         $this->add_submission($student, $assign);
1877         $submission = $assign->get_user_submission($student->id, 0);
1878         $assign->testable_update_submission($submission, $student->id, true, false);
1880         $this->setUser($teacher);
1882         // Verify the gradebook update.
1883         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1885         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1886         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1887     }
1889     public function test_update_submission_blind() {
1890         $this->resetAfterTest();
1892         $course = $this->getDataGenerator()->create_course();
1893         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1894         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1896         $assign = $this->create_instance($course, [
1897             'blindmarking' => 1,
1898         ]);
1900         $this->add_submission($student, $assign);
1901         $submission = $assign->get_user_submission($student->id, 0);
1902         $assign->testable_update_submission($submission, $student->id, true, false);
1904         // Verify the gradebook update.
1905         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1907         // The usermodified is not set because this is blind marked.
1908         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1909         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1910     }
1912     public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
1913         global $PAGE;
1915         $this->resetAfterTest();
1917         $course = $this->getDataGenerator()->create_course();
1918         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1919         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1921         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1922         groups_add_member($group, $student);
1924         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1925         groups_add_member($group, $otherstudent);
1927         $assign = $this->create_instance($course, [
1928             'teamsubmission' => 1,
1929             'assignsubmission_onlinetext_enabled' => 1,
1930             'submissiondrafts' => 1,
1931             'requireallteammemberssubmit' => 1,
1932         ]);
1934         // Now verify group assignments.
1935         $this->setUser($teacher);
1936         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1938         // Add a submission.
1939         $this->add_submission($student, $assign);
1941         // Check we can see the submit button.
1942         $this->setUser($student);
1943         $output = $assign->view_student_summary($student, true);
1944         $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1946         $submission = $assign->get_group_submission($student->id, 0, true);
1947         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1948         $assign->testable_update_submission($submission, $student->id, true, true);
1950         // Check that the student does not see "Submit" button.
1951         $output = $assign->view_student_summary($student, true);
1952         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1954         // Change to another user in the same group.
1955         $this->setUser($otherstudent);
1956         $output = $assign->view_student_summary($otherstudent, true);
1957         $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1959         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
1960         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1961         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
1962         $output = $assign->view_student_summary($otherstudent, true);
1963         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1964     }
1966     public function test_group_submissions_submit_for_marking() {
1967         global $PAGE;
1969         $this->resetAfterTest();
1971         $course = $this->getDataGenerator()->create_course();
1972         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1973         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1975         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1976         groups_add_member($group, $student);
1978         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1979         groups_add_member($group, $otherstudent);
1981         // Now verify group assignments.
1982         $this->setUser($teacher);
1983         $time = time();
1984         $assign = $this->create_instance($course, [
1985             'teamsubmission' => 1,
1986             'assignsubmission_onlinetext_enabled' => 1,
1987             'submissiondrafts' => 1,
1988             'requireallteammemberssubmit' => 0,
1989             'duedate' => $time - (2 * DAYSECS),
1990         ]);
1991         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1993         // Add a submission.
1994         $this->add_submission($student, $assign);
1996         // Check we can see the submit button.
1997         $output = $assign->view_student_summary($student, true);
1998         $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1999         $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2000         $difftime = time() - $time;
2001         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2003         $submission = $assign->get_group_submission($student->id, 0, true);
2004         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2005         $assign->testable_update_submission($submission, $student->id, true, true);
2007         // Check that the student does not see "Submit" button.
2008         $output = $assign->view_student_summary($student, true);
2009         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2011         // Change to another user in the same group.
2012         $this->setUser($otherstudent);
2013         $output = $assign->view_student_summary($otherstudent, true);
2014         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2016         // Check that time remaining is not overdue.
2017         $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2018         $difftime = time() - $time;
2019         $this->assertStringContainsString(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2021         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2022         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2023         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2024         $output = $assign->view_student_summary($otherstudent, true);
2025         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2026     }
2028     public function test_submissions_open() {
2029         global $DB;
2031         $this->resetAfterTest();
2033         $course = $this->getDataGenerator()->create_course();
2034         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2035         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2036         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2037         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2038         $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
2039             $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
2041         $this->setAdminUser();
2043         $now = time();
2044         $tomorrow = $now + DAYSECS;
2045         $oneweek = $now + WEEKSECS;
2046         $yesterday = $now - DAYSECS;
2048         $assign = $this->create_instance($course);
2049         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2051         $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
2052         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2054         $assign = $this->create_instance($course, ['duedate' => $yesterday]);
2055         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2057         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
2058         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2060         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2061         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2063         $assign->testable_save_user_extension($student->id, $tomorrow);
2064         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2066         $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2067         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2069         $this->setUser($student);
2070         $submission = $assign->get_user_submission($student->id, true);
2071         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2072         $assign->testable_update_submission($submission, $student->id, true, false);
2074         $this->setUser($teacher);
2075         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2076     }
2078     public function test_get_graders() {
2079         global $DB;
2081         $this->resetAfterTest();
2083         $course = $this->getDataGenerator()->create_course();
2084         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2085         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2086         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2088         $this->setAdminUser();
2090         // Create an assignment with no groups.
2091         $assign = $this->create_instance($course);
2092         $this->assertCount(2, $assign->testable_get_graders($student->id));
2093     }
2095     public function test_get_graders_separate_groups() {
2096         global $DB;
2098         $this->resetAfterTest();
2100         $course = $this->getDataGenerator()->create_course();
2101         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2102         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2103         $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2104         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2105         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2106         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2108         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2109         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2110         groups_add_member($group1, $student);
2112         $this->setAdminUser();
2114         // Force create an assignment with SEPARATEGROUPS.
2115         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2116         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2118         $assign = $this->create_instance($course, [
2119             'groupingid' => $grouping->id,
2120             'groupmode' => SEPARATEGROUPS,
2121         ]);
2123         $this->assertCount(4, $assign->testable_get_graders($student->id));
2125         // Note the second student is in a group that is not in the grouping.
2126         // This means that we get all graders that are not in a group in the grouping.
2127         $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2128     }
2130     public function test_get_notified_users() {
2131         global $CFG, $DB;
2133         $this->resetAfterTest();
2135         $course = $this->getDataGenerator()->create_course();
2136         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2137         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2138         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2140         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2141         groups_add_member($group1, $teacher);
2143         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2144         groups_add_member($group1, $editingteacher);
2146         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2147         groups_add_member($group1, $student);
2149         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2150         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2152         $capability = 'mod/assign:receivegradernotifications';
2153         $coursecontext = context_course::instance($course->id);
2154         $role = $DB->get_record('role', array('shortname' => 'teacher'));
2156         $this->setUser($teacher);
2158         // Create an assignment with no groups.
2159         $assign = $this->create_instance($course);
2161         $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2163         // Change nonediting teachers role to not receive grader notifications.
2164         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2166         // Only the editing teachers will be returned.
2167         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2169         // Note the second student is in a group that is not in the grouping.
2170         // This means that we get all graders that are not in a group in the grouping.
2171         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2172     }
2174     public function test_get_notified_users_in_grouping() {
2175         global $CFG, $DB;
2177         $this->resetAfterTest();
2179         $course = $this->getDataGenerator()->create_course();
2180         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2181         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2182         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2184         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2185         groups_add_member($group1, $teacher);
2187         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2188         groups_add_member($group1, $editingteacher);
2190         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2191         groups_add_member($group1, $student);
2193         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2194         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2196         // Force create an assignment with SEPARATEGROUPS.
2197         $assign = $this->create_instance($course, [
2198             'groupingid' => $grouping->id,
2199             'groupmode' => SEPARATEGROUPS,
2200         ]);
2202         // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2203         $this->setUser($student);
2204         $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2206         // Note the second student is in a group that is not in the grouping.
2207         // This means that we get all graders that are not in a group in the grouping.
2208         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2210         // Change nonediting teachers role to not receive grader notifications.
2211         $capability = 'mod/assign:receivegradernotifications';
2212         $coursecontext = context_course::instance($course->id);
2213         $role = $DB->get_record('role', ['shortname' => 'teacher']);
2214         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2216         // Only the editing teachers will be returned.
2217         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2219         // Note the second student is in a group that is not in the grouping.
2220         // This means that we get all graders that are not in a group in the grouping.
2221         // Unfortunately there are no editing teachers who are not in a group.
2222         $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2223     }
2225     public function test_group_members_only() {
2226         global $CFG;
2228         $this->resetAfterTest();
2230         $course = $this->getDataGenerator()->create_course();
2231         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2232         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2233         $this->getDataGenerator()->create_grouping_group([
2234             'groupid' => $group1->id,
2235             'groupingid' => $grouping->id,
2236         ]);
2238         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2239         $this->getDataGenerator()->create_grouping_group([
2240             'groupid' => $group2->id,
2241             'groupingid' => $grouping->id,
2242         ]);
2244         $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2246         // Add users in the following groups
2247         // - Teacher - Group 1.
2248         // - Student - Group 1.
2249         // - Student - Group 2.
2250         // - Student - Unrelated Group
2251         // - Student - No group.
2252         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2253         groups_add_member($group1, $teacher);
2255         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2256         groups_add_member($group1, $student);
2258         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2259         groups_add_member($group2, $otherstudent);
2261         $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2262         groups_add_member($group2, $otherstudent);
2264         $this->getDataGenerator()->create_and_enrol($course, 'student');
2266         $this->setAdminUser();
2268         $CFG->enableavailability = true;
2269         $assign = $this->create_instance($course, [], [
2270             'availability' => json_encode(
2271                 \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2272             ),
2273             'groupingid' => $grouping->id,
2274         ]);
2276         // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2277         // group, or the student in an unrelated group.
2278         $this->setUser($teacher);
2279         $participants = $assign->list_participants(0, true);
2280         $this->assertCount(2, $participants);
2281         $this->assertTrue(isset($participants[$student->id]));
2282         $this->assertTrue(isset($participants[$otherstudent->id]));
2283     }
2285     public function test_get_uniqueid_for_user() {
2286         $this->resetAfterTest();
2288         $course = $this->getDataGenerator()->create_course();
2289         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2290         $students = [];
2291         for ($i = 0; $i < 10; $i++) {
2292             $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2293             $students[$student->id] = $student;
2294         }
2296         $this->setUser($teacher);
2297         $assign = $this->create_instance($course);
2299         foreach ($students as $student) {
2300             $uniqueid = $assign->get_uniqueid_for_user($student->id);
2301             $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2302         }
2303     }
2305     public function test_show_student_summary() {
2306         global $CFG, $PAGE;
2308         $this->resetAfterTest();
2310         $course = $this->getDataGenerator()->create_course();
2311         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2312         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2313         $this->setUser($teacher);
2314         $assign = $this->create_instance($course);
2315         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2317         // No feedback should be available because this student has not been graded.
2318         $this->setUser($student);
2319         $output = $assign->view_student_summary($student, true);
2320         $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if there is no grade');
2322         // Simulate adding a grade.
2323         $this->add_submission($student, $assign);
2324         $this->submit_for_grading($student, $assign);
2325         $this->mark_submission($teacher, $assign, $student);
2327         // Now we should see the feedback.
2328         $this->setUser($student);
2329         $output = $assign->view_student_summary($student, true);
2330         $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
2332         // Now hide the grade in gradebook.
2333         $this->setUser($teacher);
2334         require_once($CFG->libdir.'/gradelib.php');
2335         $gradeitem = new grade_item(array(
2336             'itemtype'      => 'mod',
2337             'itemmodule'    => 'assign',
2338             'iteminstance'  => $assign->get_instance()->id,
2339             'courseid'      => $course->id));
2341         $gradeitem->set_hidden(1, false);
2343         // No feedback should be available because the grade is hidden.
2344         $this->setUser($student);
2345         $output = $assign->view_student_summary($student, true);
2346         $this->assertDoesNotMatchRegularExpression('/Feedback/', $output,
2347             'Do not show feedback if the grade is hidden in the gradebook');
2348     }
2350     public function test_show_student_summary_with_feedback() {
2351         global $CFG, $PAGE;
2353         $this->resetAfterTest();
2355         $course = $this->getDataGenerator()->create_course();
2356         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2357         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2358         $this->setUser($teacher);
2359         $assign = $this->create_instance($course, [
2360             'assignfeedback_comments_enabled' => 1
2361         ]);
2362         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2364         // No feedback should be available because this student has not been graded.
2365         $this->setUser($student);
2366         $output = $assign->view_student_summary($student, true);
2367         $this->assertDoesNotMatchRegularExpression('/Feedback/', $output);
2369         // Simulate adding a grade.
2370         $this->add_submission($student, $assign);
2371         $this->submit_for_grading($student, $assign);
2372         $this->mark_submission($teacher, $assign, $student, null, [
2373             'assignfeedbackcomments_editor' => [
2374                 'text' => 'Tomato sauce',
2375                 'format' => FORMAT_MOODLE,
2376             ],
2377         ]);
2379         // Should have feedback but no grade.
2380         $this->setUser($student);
2381         $output = $assign->view_student_summary($student, true);
2382         $this->assertMatchesRegularExpression('/Feedback/', $output);
2383         $this->assertMatchesRegularExpression('/Tomato sauce/', $output);
2384         $this->assertDoesNotMatchRegularExpression('/Grade/', $output, 'Do not show grade when there is no grade.');
2385         $this->assertDoesNotMatchRegularExpression('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2387         // Add a grade now.
2388         $this->mark_submission($teacher, $assign, $student, 50.0, [
2389             'assignfeedbackcomments_editor' => [
2390                 'text' => 'Bechamel sauce',
2391                 'format' => FORMAT_MOODLE,
2392             ],
2393         ]);
2395         // Should have feedback but no grade.
2396         $this->setUser($student);
2397         $output = $assign->view_student_summary($student, true);
2398         $this->assertDoesNotMatchRegularExpression('/Tomato sauce/', $output);
2399         $this->assertMatchesRegularExpression('/Bechamel sauce/', $output);
2400         $this->assertMatchesRegularExpression('/Grade/', $output);
2401         $this->assertMatchesRegularExpression('/Graded on/', $output);
2403         // Now hide the grade in gradebook.
2404         $this->setUser($teacher);
2405         $gradeitem = new grade_item(array(
2406             'itemtype'      => 'mod',
2407             'itemmodule'    => 'assign',
2408             'iteminstance'  => $assign->get_instance()->id,
2409             'courseid'      => $course->id));
2411         $gradeitem->set_hidden(1, false);
2413         // No feedback should be available because the grade is hidden.
2414         $this->setUser($student);
2415         $output = $assign->view_student_summary($student, true);
2416         $this->assertDoesNotMatchRegularExpression('/Feedback/', $output,
2417             'Do not show feedback if the grade is hidden in the gradebook');
2418     }
2420     /**
2421      * Test reopen behavior when in "Manual" mode.
2422      */
2423     public function test_attempt_reopen_method_manual() {
2424         global $PAGE;
2426         $this->resetAfterTest();
2427         $course = $this->getDataGenerator()->create_course();
2428         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2429         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2431         $assign = $this->create_instance($course, [
2432             'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2433             'maxattempts' => 3,
2434             'submissiondrafts' => 1,
2435             'assignsubmission_onlinetext_enabled' => 1,
2436         ]);
2437         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2439         // Student should be able to see an add submission button.
2440         $this->setUser($student);
2441         $output = $assign->view_student_summary($student, true);
2442         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2444         // Add a submission.
2445         $this->add_submission($student, $assign);
2446         $this->submit_for_grading($student, $assign);
2448         // Verify the student cannot make changes to the submission.
2449         $output = $assign->view_student_summary($student, true);
2450         $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2452         // Mark the submission.
2453         $this->mark_submission($teacher, $assign, $student);
2455         // Check the student can see the grade.
2456         $this->setUser($student);
2457         $output = $assign->view_student_summary($student, true);
2458         $this->assertNotEquals(false, strpos($output, '50.0'));
2460         // Allow the student another attempt.
2461         $teacher->ignoresesskey = true;
2462         $this->setUser($teacher);
2463         $result = $assign->testable_process_add_attempt($student->id);
2464         $this->assertEquals(true, $result);
2466         // Check that the previous attempt is now in the submission history table.
2467         $this->setUser($student);
2468         $output = $assign->view_student_summary($student, true);
2469         // Need a better check.
2470         $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2472         // Check that the student now has a button for Add a new attempt".
2473         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2474         // Check that the student now does not have a button for Submit.
2475         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2477         // Check that the student now has a submission history.
2478         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2480         $this->setUser($teacher);
2481         // Check that the grading table loads correctly and contains this user.
2482         // This is also testing that we do not get duplicate rows in the grading table.
2483         $gradingtable = new assign_grading_table($assign, 100, '', 0, true);
2484         $output = $assign->get_renderer()->render($gradingtable);
2485         $this->assertEquals(true, strpos($output, $student->lastname));
2487         // Should be 1 not 2.
2488         $this->assertEquals(1, $assign->count_submissions());
2489         $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2490         $this->assertEquals(0, $assign->count_submissions_need_grading());
2491         $this->assertEquals(1, $assign->count_grades());
2493         // Change max attempts to unlimited.
2494         $formdata = clone($assign->get_instance());
2495         $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2496         $formdata->instance = $formdata->id;
2497         $assign->update_instance($formdata);
2499         // Mark the submission again.
2500         $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2502         // Check the grade exists.
2503         $this->setUser($teacher);
2504         $grades = $assign->get_user_grades_for_gradebook($student->id);
2505         $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2507         // Check we can reopen still.
2508         $result = $assign->testable_process_add_attempt($student->id);
2509         $this->assertEquals(true, $result);
2511         // Should no longer have a grade because there is no grade for the latest attempt.
2512         $grades = $assign->get_user_grades_for_gradebook($student->id);
2513         $this->assertEmpty($grades);
2514     }
2516     /**
2517      * Test reopen behavior when in "Reopen until pass" mode.
2518      */
2519     public function test_attempt_reopen_method_untilpass() {
2520         global $PAGE;
2522         $this->resetAfterTest();
2523         $course = $this->getDataGenerator()->create_course();
2524         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2525         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2527         $assign = $this->create_instance($course, [
2528             'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2529             'maxattempts' => 3,
2530             'submissiondrafts' => 1,
2531             'assignsubmission_onlinetext_enabled' => 1,
2532         ]);
2533         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2535         // Set grade to pass to 80.
2536         $gradeitem = $assign->get_grade_item();
2537         $gradeitem->gradepass = '80.0';
2538         $gradeitem->update();
2540         // Student should be able to see an add submission button.
2541         $this->setUser($student);
2542         $output = $assign->view_student_summary($student, true);
2543         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2545         // Add a submission.
2546         $this->add_submission($student, $assign);
2547         $this->submit_for_grading($student, $assign);
2549         // Verify the student cannot make a new attempt.
2550         $output = $assign->view_student_summary($student, true);
2551         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2553         // Mark the submission as non-passing.
2554         $this->mark_submission($teacher, $assign, $student, 50.0);
2556         // Check the student can see the grade.
2557         $this->setUser($student);
2558         $output = $assign->view_student_summary($student, true);
2559         $this->assertNotEquals(false, strpos($output, '50.0'));
2561         // Check that the student now has a button for Add a new attempt.
2562         $output = $assign->view_student_summary($student, true);
2563         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2565         // Check that the student now does not have a button for Submit.
2566         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2568         // Check that the student now has a submission history.
2569         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2571         // Add a second submission.
2572         $this->add_submission($student, $assign);
2573         $this->submit_for_grading($student, $assign);
2575         // Mark the submission as passing.
2576         $this->mark_submission($teacher, $assign, $student, 80.0);
2578         // Check that the student does not have a button for Add a new attempt.
2579         $this->setUser($student);
2580         $output = $assign->view_student_summary($student, true);
2581         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2583         // Re-mark the submission as not passing.
2584         $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2586         // Check that the student now has a button for Add a new attempt.
2587         $this->setUser($student);
2588         $output = $assign->view_student_summary($student, true);
2589         $this->assertMatchesRegularExpression('/' . get_string('addnewattempt', 'assign') . '/', $output);
2590         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2591     }
2593     public function test_attempt_reopen_method_untilpass_passing() {
2594         global $PAGE;
2596         $this->resetAfterTest();
2597         $course = $this->getDataGenerator()->create_course();
2598         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2599         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2601         $assign = $this->create_instance($course, [
2602             'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2603             'maxattempts' => 3,
2604             'submissiondrafts' => 1,
2605             'assignsubmission_onlinetext_enabled' => 1,
2606         ]);
2607         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2609         // Set grade to pass to 80.
2610         $gradeitem = $assign->get_grade_item();
2611         $gradeitem->gradepass = '80.0';
2612         $gradeitem->update();
2614         // Student should be able to see an add submission button.
2615         $this->setUser($student);
2616         $output = $assign->view_student_summary($student, true);
2617         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2619         // Add a submission as a student.
2620         $this->add_submission($student, $assign);
2621         $this->submit_for_grading($student, $assign);
2623         // Mark the submission as passing.
2624         $this->mark_submission($teacher, $assign, $student, 100.0);
2626         // Check the student can see the grade.
2627         $this->setUser($student);
2628         $output = $assign->view_student_summary($student, true);
2629         $this->assertNotEquals(false, strpos($output, '100.0'));
2631         // Check that the student does not have a button for Add a new attempt.
2632         $output = $assign->view_student_summary($student, true);
2633         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2634     }
2636     public function test_attempt_reopen_method_untilpass_no_passing_requirement() {
2637         global $PAGE;
2639         $this->resetAfterTest();
2640         $course = $this->getDataGenerator()->create_course();
2641         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2642         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2644         $assign = $this->create_instance($course, [
2645             'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2646             'maxattempts' => 3,
2647             'submissiondrafts' => 1,
2648             'assignsubmission_onlinetext_enabled' => 1,
2649         ]);
2650         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2652         // Set grade to pass to 0, so that no attempts should reopen.
2653         $gradeitem = $assign->get_grade_item();
2654         $gradeitem->gradepass = '0';
2655         $gradeitem->update();
2657         // Student should be able to see an add submission button.
2658         $this->setUser($student);
2659         $output = $assign->view_student_summary($student, true);
2660         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2662         // Add a submission.
2663         $this->add_submission($student, $assign);
2664         $this->submit_for_grading($student, $assign);
2666         // Mark the submission with any grade.
2667         $this->mark_submission($teacher, $assign, $student, 0.0);
2669         // Check the student can see the grade.
2670         $this->setUser($student);
2671         $output = $assign->view_student_summary($student, true);
2672         $this->assertNotEquals(false, strpos($output, '0.0'));
2674         // Check that the student does not have a button for Add a new attempt.
2675         $output = $assign->view_student_summary($student, true);
2676         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2677     }
2679     /**
2680      * Test student visibility for each stage of the marking workflow.
2681      */
2682     public function test_markingworkflow() {
2683         global $PAGE;
2685         $this->resetAfterTest();
2686         $course = $this->getDataGenerator()->create_course();
2687         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2688         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2690         $assign = $this->create_instance($course, [
2691             'markingworkflow' => 1,
2692         ]);
2694         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2696         // Mark the submission and set to notmarked.
2697         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2698             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2699         ]);
2701         // Check the student can't see the grade.
2702         $this->setUser($student);
2703         $output = $assign->view_student_summary($student, true);
2704         $this->assertEquals(false, strpos($output, '50.0'));
2706         // Make sure the grade isn't pushed to the gradebook.
2707         $grades = $assign->get_user_grades_for_gradebook($student->id);
2708         $this->assertEmpty($grades);
2710         // Mark the submission and set to inmarking.
2711         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2712             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2713         ]);
2715         // Check the student can't see the grade.
2716         $this->setUser($student);
2717         $output = $assign->view_student_summary($student, true);
2718         $this->assertEquals(false, strpos($output, '50.0'));
2720         // Make sure the grade isn't pushed to the gradebook.
2721         $grades = $assign->get_user_grades_for_gradebook($student->id);
2722         $this->assertEmpty($grades);
2724         // Mark the submission and set to readyforreview.
2725         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2726             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2727         ]);
2729         // Check the student can't see the grade.
2730         $this->setUser($student);
2731         $output = $assign->view_student_summary($student, true);
2732         $this->assertEquals(false, strpos($output, '50.0'));
2734         // Make sure the grade isn't pushed to the gradebook.
2735         $grades = $assign->get_user_grades_for_gradebook($student->id);
2736         $this->assertEmpty($grades);
2738         // Mark the submission and set to inreview.
2739         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2740             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2741         ]);
2743         // Check the student can't see the grade.
2744         $this->setUser($student);
2745         $output = $assign->view_student_summary($student, true);
2746         $this->assertEquals(false, strpos($output, '50.0'));
2748         // Make sure the grade isn't pushed to the gradebook.
2749         $grades = $assign->get_user_grades_for_gradebook($student->id);
2750         $this->assertEmpty($grades);
2752         // Mark the submission and set to readyforrelease.
2753         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2754             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
2755         ]);
2757         // Check the student can't see the grade.
2758         $this->setUser($student);
2759         $output = $assign->view_student_summary($student, true);
2760         $this->assertEquals(false, strpos($output, '50.0'));
2762         // Make sure the grade isn't pushed to the gradebook.
2763         $grades = $assign->get_user_grades_for_gradebook($student->id);
2764         $this->assertEmpty($grades);
2766         // Mark the submission and set to released.
2767         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2768             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
2769         ]);
2771         // Check the student can see the grade.
2772         $this->setUser($student);
2773         $output = $assign->view_student_summary($student, true);
2774         $this->assertNotEquals(false, strpos($output, '50.0'));
2776         // Make sure the grade is pushed to the gradebook.
2777         $grades = $assign->get_user_grades_for_gradebook($student->id);
2778         $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
2779     }
2781     /**
2782      * Test that a student allocated a specific marker is only shown to that marker.
2783      */
2784     public function test_markerallocation() {
2785         global $PAGE;
2787         $this->resetAfterTest();
2788         $course = $this->getDataGenerator()->create_course();
2789         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2790         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2791         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2793         $assign = $this->create_instance($course, [
2794             'markingworkflow' => 1,
2795             'markingallocation' => 1
2796         ]);
2798         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2800         // Allocate marker to submission.
2801         $this->mark_submission($teacher, $assign, $student, null, [
2802             'allocatedmarker' => $teacher->id,
2803         ]);
2805         // Check the allocated marker can view the submission.
2806         $this->setUser($teacher);
2807         $users = $assign->list_participants(0, true);
2808         $this->assertEquals(1, count($users));
2809         $this->assertTrue(isset($users[$student->id]));
2811         $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2812         $context = context_module::instance($cm->id);
2813         $assign = new mod_assign_testable_assign($context, $cm, $course);
2815         // Check that other teachers can't view this submission.
2816         $this->setUser($otherteacher);
2817         $users = $assign->list_participants(0, true);
2818         $this->assertEquals(0, count($users));
2819     }
2821     /**
2822      * Ensure that a teacher cannot submit for students as standard.
2823      */
2824     public function test_teacher_submit_for_student() {
2825         global $PAGE;
2827         $this->resetAfterTest();
2828         $course = $this->getDataGenerator()->create_course();
2829         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2830         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2832         $assign = $this->create_instance($course, [
2833             'assignsubmission_onlinetext_enabled' => 1,
2834             'submissiondrafts' => 1,
2835         ]);
2837         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2839         // Add a submission but do not submit.
2840         $this->add_submission($student, $assign, 'Student submission text');
2842         $this->setUser($student);
2843         $output = $assign->view_student_summary($student, true);
2844         $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2846         // Check that a teacher can not edit the submission as they do not have the capability.
2847         $this->setUser($teacher);
2848         $this->expectException('moodle_exception');
2849         $this->expectExceptionMessage('error/nopermission');
2850         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2851     }
2853     /**
2854      * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
2855      */
2856     public function test_teacher_submit_for_student_with_capability() {
2857         global $PAGE;
2859         $this->resetAfterTest();
2860         $course = $this->getDataGenerator()->create_course();
2861         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2862         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2863         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2865         $assign = $this->create_instance($course, [
2866             'assignsubmission_onlinetext_enabled' => 1,
2867             'submissiondrafts' => 1,
2868         ]);
2870         // Add the required capability.
2871         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2872         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2873         role_assign($roleid, $teacher->id, $assign->get_context()->id);
2874         accesslib_clear_all_caches_for_unit_testing();
2876         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2878         // Add a submission but do not submit.
2879         $this->add_submission($student, $assign, 'Student submission text');
2881         $this->setUser($student);
2882         $output = $assign->view_student_summary($student, true);
2883         $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2885         // Check that a teacher can edit the submission.
2886         $this->setUser($teacher);
2887         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2889         $this->setUser($student);
2890         $output = $assign->view_student_summary($student, true);
2891         $this->assertStringNotContainsString('Student submission text', $output, 'Contains student submission text');
2892         $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains teacher edited submission text');
2894         // Check that the teacher can submit the students work.
2895         $this->setUser($teacher);
2896         $this->submit_for_grading($student, $assign, [], false);
2898         // Revert to draft so the student can edit it.
2899         $assign->revert_to_draft($student->id);
2901         $this->setUser($student);
2903         // Check that the submission text was saved.
2904         $output = $assign->view_student_summary($student, true);
2905         $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains student submission text');
2907         // Check that the student can submit their work.
2908         $this->submit_for_grading($student, $assign, []);
2910         $output = $assign->view_student_summary($student, true);
2911         $this->assertStringNotContainsString(get_string('addsubmission', 'assign'), $output);
2913         // An editing teacher without the extra role should still be able to revert to draft.
2914         $this->setUser($otherteacher);
2916         // Revert to draft so the submission is editable.
2917         $assign->revert_to_draft($student->id);
2918     }
2920     /**
2921      * Ensure that disabling submit after the cutoff date works as expected.
2922      */
2923     public function test_disable_submit_after_cutoff_date() {
2924         global $PAGE;
2926         $this->resetAfterTest();
2927         $course = $this->getDataGenerator()->create_course();
2928         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2930         $now = time();
2931         $tomorrow = $now + DAYSECS;
2932         $lastweek = $now - (7 * DAYSECS);
2933         $yesterday = $now - DAYSECS;
2935         $this->setAdminUser();
2936         $assign = $this->create_instance($course, [
2937             'duedate' => $yesterday,
2938             'cutoffdate' => $tomorrow,
2939             'assignsubmission_onlinetext_enabled' => 1,
2940         ]);
2942         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2944         // Student should be able to see an add submission button.
2945         $this->setUser($student);
2946         $output = $assign->view_student_summary($student, true);
2947         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2949         // Add a submission but don't submit now.
2950         $this->add_submission($student, $assign);
2952         // Create another instance with cut-off and due-date already passed.
2953         $this->setAdminUser();
2954         $assign = $this->create_instance($course, [
2955             'duedate' => $lastweek,
2956             'cutoffdate' => $yesterday,
2957             'assignsubmission_onlinetext_enabled' => 1,
2958         ]);
2960         $this->setUser($student);
2961         $output = $assign->view_student_summary($student, true);
2962         $this->assertStringNotContainsString($output, get_string('editsubmission', 'assign'),
2963             'Should not be able to edit after cutoff date.');
2964         $this->assertStringNotContainsString($output, get_string('submitassignment', 'assign'),
2965             'Should not be able to submit after cutoff date.');
2966     }
2968     /**
2969      * Testing for submission comment plugin settings.
2970      *
2971      * @dataProvider submission_plugin_settings_provider
2972      * @param   bool    $globalenabled
2973      * @param   array   $instanceconfig
2974      * @param   bool    $isenabled
2975      */
2976     public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled) {
2977         global $CFG;
2979         $this->resetAfterTest();
2980         $course = $this->getDataGenerator()->create_course();
2982         $CFG->usecomments = $globalenabled;
2983         $assign = $this->create_instance($course, $instanceconfig);
2984         $plugin = $assign->get_submission_plugin_by_type('comments');
2985         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
2986     }
2988     public function submission_plugin_settings_provider() {
2989         return [
2990             'CFG->usecomments true, empty config => Enabled by default' => [
2991                 true,
2992                 [],
2993                 true,
2994             ],
2995             'CFG->usecomments true, config enabled => Comments enabled' => [
2996                 true,
2997                 [
2998                     'assignsubmission_comments_enabled' => 1,
2999                 ],
3000                 true,
3001             ],
3002             'CFG->usecomments true, config idisabled => Comments enabled' => [
3003                 true,
3004                 [
3005                     'assignsubmission_comments_enabled' => 0,
3006                 ],
3007                 true,
3008             ],
3009             'CFG->usecomments false, empty config => Disabled by default' => [
3010                 false,
3011                 [],
3012                 false,
3013             ],
3014             'CFG->usecomments false, config enabled => Comments disabled' => [
3015                 false,
3016                 [
3017                     'assignsubmission_comments_enabled' => 1,
3018                 ],
3019                 false,
3020             ],
3021             'CFG->usecomments false, config disabled => Comments disabled' => [
3022                 false,
3023                 [
3024                     'assignsubmission_comments_enabled' => 0,
3025                 ],
3026                 false,
3027             ],
3028         ];
3029     }
3031     /**
3032      * Testing for comment inline settings
3033      */
3034     public function test_feedback_comment_commentinline() {
3035         global $CFG, $USER;
3037         $this->resetAfterTest();
3038         $course = $this->getDataGenerator()->create_course();
3039         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3040         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3042         $sourcetext = "Hello!
3044 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3046 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3047 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3049 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
3050 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
3051 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
3052 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
3053 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
3054 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3055 ";
3057         $this->setUser($teacher);
3058         $assign = $this->create_instance($course, [
3059             'assignsubmission_onlinetext_enabled' => 1,
3060             'assignfeedback_comments_enabled' => 1,
3061             'assignfeedback_comments_commentinline' => 1,
3062         ]);
3064         $this->setUser($student);
3066         // Add a submission but don't submit now.
3067         $this->add_submission($student, $assign, $sourcetext);
3069         $this->setUser($teacher);
3071         $data = new stdClass();
3072         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3073         $pagination = [
3074             'userid' => $student->id,
3075             'rownum' => 0,
3076             'last' => true,
3077             'useridlistid' => $assign->get_useridlist_key_id(),
3078             'attemptnumber' => 0,
3079         ];
3080         $formparams = array($assign, $data, $pagination);
3081         $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
3083         // We need to get the URL these will be transformed to.
3084         $context = context_user::instance($USER->id);
3085         $itemid = $data->assignfeedbackcomments_editor['itemid'];
3086         $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid;
3088         // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
3089         $filteredtext = "Hello!
3091 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3093 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3094 Plugin url outside a tag: $url/logo-240x60.gif
3096 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3097 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3098 Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" />
3099 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" />
3100 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a>
3101 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3102 ";
3104         $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3105     }
3107     /**
3108      * Testing for feedback comment plugin settings.
3109      *
3110      * @dataProvider feedback_plugin_settings_provider
3111      * @param   array   $instanceconfig
3112      * @param   bool    $isenabled
3113      */
3114     public function test_feedback_plugin_settings($instanceconfig, $isenabled) {
3115         $this->resetAfterTest();
3116         $course = $this->getDataGenerator()->create_course();
3118         $assign = $this->create_instance($course, $instanceconfig);
3119         $plugin = $assign->get_feedback_plugin_by_type('comments');
3120         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3121     }
3123     public function feedback_plugin_settings_provider() {
3124         return [
3125             'No configuration => disabled' => [
3126                 [],
3127                 false,
3128             ],
3129             'Actively disabled' => [
3130                 [
3131                     'assignfeedback_comments_enabled' => 0,
3132                 ],
3133                 false,
3134             ],
3135             'Actively enabled' => [
3136                 [
3137                     'assignfeedback_comments_enabled' => 1,
3138                 ],
3139                 true,
3140             ],
3141         ];
3142     }
3144     /**
3145      * Testing if gradebook feedback plugin is enabled.
3146      */
3147     public function test_is_gradebook_feedback_enabled() {
3148         $this->resetAfterTest();
3149         $course = $this->getDataGenerator()->create_course();
3150         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3151         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3153         $adminconfig = get_config('assign');
3154         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3156         // Create assignment with gradebook feedback enabled and grade = 0.
3157         $assign = $this->create_instance($course, [
3158             "{$gradebookplugin}_enabled" => 1,
3159             'grades' => 0,
3160         ]);
3162         // Get gradebook feedback plugin.
3163         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3164         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3165         $this->assertEquals(1, $plugin->is_enabled('enabled'));
3166         $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3167     }
3169     /**
3170      * Testing if gradebook feedback plugin is disabled.
3171      */
3172     public function test_is_gradebook_feedback_disabled() {
3173         $this->resetAfterTest();
3174         $course = $this->getDataGenerator()->create_course();
3175         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3176         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3178         $adminconfig = get_config('assign');
3179         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3181         // Create assignment with gradebook feedback disabled and grade = 0.
3182         $assign = $this->create_instance($course, [
3183             "{$gradebookplugin}_enabled" => 0,
3184             'grades' => 0,
3185         ]);
3187         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3188         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3189         $this->assertEquals(0, $plugin->is_enabled('enabled'));
3190     }
3192     /**
3193      * Testing can_edit_submission.
3194      */
3195     public function test_can_edit_submission() {
3196         $this->resetAfterTest();
3197         $course = $this->getDataGenerator()->create_course();
3198         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3199         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3200         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3202         $assign = $this->create_instance($course, [
3203             'assignsubmission_onlinetext_enabled' => 1,
3204             'submissiondrafts' => 1,
3205         ]);
3207         // Check student can edit their own submission.
3208         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3210         // Check student cannot edit others submission.
3211         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3213         // Check teacher cannot (by default) edit a students submission.
3214         $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3215     }
3217     /**
3218      * Testing can_edit_submission with the editothersubmission capability.
3219      */
3220     public function test_can_edit_submission_with_editothersubmission() {
3221         $this->resetAfterTest();
3222         $course = $this->getDataGenerator()->create_course();
3223         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3224         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3225         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3227         $assign = $this->create_instance($course, [
3228             'assignsubmission_onlinetext_enabled' => 1,
3229             'submissiondrafts' => 1,
3230         ]);
3232         // Add the required capability to edit a student submission.
3233         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3234         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3235         role_assign($roleid, $teacher->id, $assign->get_context()->id);
3236         accesslib_clear_all_caches_for_unit_testing();
3238         // Check student can edit their own submission.
3239         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3241         // Check student cannot edit others submission.
3242         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3244         // Retest - should now have access.
3245         $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3246     }
3248     /**
3249      * Testing can_edit_submission
3250      */
3251     public function test_can_edit_submission_separategroups() {
3252         $this->resetAfterTest();
3253         $course = $this->getDataGenerator()->create_course();
3254         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3256         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3257         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3258         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3259         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3261         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3262         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3263         groups_assign_grouping($grouping->id, $group1->id);
3264         groups_add_member($group1, $student1);
3265         groups_add_member($group1, $student2);
3267         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3268         groups_assign_grouping($grouping->id, $group2->id);
3269         groups_add_member($group2, $student3);
3270         groups_add_member($group2, $student4);
3272         $assign = $this->create_instance($course, [
3273             'assignsubmission_onlinetext_enabled' => 1,
3274             'submissiondrafts' => 1,
3275             'groupingid' => $grouping->id,
3276             'groupmode' => SEPARATEGROUPS,
3277         ]);
3279         // Verify a student does not have the ability to edit submissions for other users.
3280         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3281         $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id));
3282         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3283         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3284     }
3286     /**
3287      * Testing can_edit_submission
3288      */
3289     public function test_can_edit_submission_separategroups_with_editothersubmission() {
3290         $this->resetAfterTest();
3291         $course = $this->getDataGenerator()->create_course();
3292         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3294         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3295         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3296         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3297         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3299         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3300         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3301         groups_assign_grouping($grouping->id, $group1->id);
3302         groups_add_member($group1, $student1);
3303         groups_add_member($group1, $student2);
3305         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3306         groups_assign_grouping($grouping->id, $group2->id);
3307         groups_add_member($group2, $student3);
3308         groups_add_member($group2, $student4);
3310         $assign = $this->create_instance($course, [
3311             'assignsubmission_onlinetext_enabled' => 1,
3312             'submissiondrafts' => 1,
3313             'groupingid' => $grouping->id,
3314             'groupmode' => SEPARATEGROUPS,
3315         ]);
3317         // Add the capability to the new assignment for student 1.
3318         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3319         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3320         role_assign($roleid, $student1->id, $assign->get_context()->id);
3321         accesslib_clear_all_caches_for_unit_testing();
3323         // Verify student1 has the ability to edit submissions for other users in their group, but not other groups.
3324         $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3325         $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id));
3326         $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3327         $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));