Merge branch 'MDL-64336' of https://github.com/NeillM/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     /**
1069      * Tests that if a student with no submission who can no longer submit is not a participant.
1070      */
1071     public function test_get_participant_with_no_submission_no_capability() {
1072         global $DB;
1073         $this->resetAfterTest();
1074         $course = self::getDataGenerator()->create_course();
1075         $coursecontext = context_course::instance($course->id);
1076         $assign = $this->create_instance($course);
1077         $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher');
1078         $student = self::getDataGenerator()->create_and_enrol($course, 'student');
1080         // Remove the students capability to submit.
1081         $role = $DB->get_field('role', 'id', ['shortname' => 'student']);
1082         assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext);
1084         $participant = $assign->get_participant($student->id);
1086         self::assertNull($participant);
1087     }
1089     /**
1090      * Tests that if a student that has submitted but can no longer submit is a participant.
1091      */
1092     public function test_get_participant_with_submission_no_capability() {
1093         global $DB;
1094         $this->resetAfterTest();
1095         $course = self::getDataGenerator()->create_course();
1096         $coursecontext = context_course::instance($course->id);
1097         $assign = $this->create_instance($course);
1098         $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher');
1099         $student = self::getDataGenerator()->create_and_enrol($course, 'student');
1101         // Simulate a submission.
1102         $this->add_submission($student, $assign);
1103         $this->submit_for_grading($student, $assign);
1105         // Remove the students capability to submit.
1106         $role = $DB->get_field('role', 'id', ['shortname' => 'student']);
1107         assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext);
1109         $participant = $assign->get_participant($student->id);
1111         self::assertNotNull($participant);
1112         self::assertEquals($student->id, $participant->id);
1113         self::assertTrue($participant->submitted);
1114         self::assertTrue($participant->requiregrading);
1115         self::assertFalse($participant->grantedextension);
1116     }
1118     public function test_get_participant_with_graded_submission() {
1119         $this->resetAfterTest();
1120         $course = $this->getDataGenerator()->create_course();
1121         $assign = $this->create_instance($course);
1122         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1123         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1125         // Simulate a submission.
1126         $this->add_submission($student, $assign);
1127         $this->submit_for_grading($student, $assign);
1129         $this->mark_submission($teacher, $assign, $student, 50.0);
1131         $data = new stdClass();
1132         $data->grade = '50.0';
1133         $assign->testable_apply_grade_to_user($data, $student->id, 0);
1135         $participant = $assign->get_participant($student->id);
1137         $this->assertEquals($student->id, $participant->id);
1138         $this->assertTrue($participant->submitted);
1139         $this->assertFalse($participant->requiregrading);
1140         $this->assertFalse($participant->grantedextension);
1141     }
1143     /**
1144      * No active group and non-group submissions disallowed => 2 groups.
1145      */
1146     public function test_count_teams_no_active_non_group_allowed() {
1147         $this->resetAfterTest();
1149         $course = $this->getDataGenerator()->create_course();
1150         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1151         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1152         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1154         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1155         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1156         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1157         groups_add_member($group1, $student1);
1158         groups_add_member($group2, $student2);
1160         $this->setUser($teacher);
1161         $assign = $this->create_instance($course, ['teamsubmission' => 1]);
1163         $this->assertEquals(2, $assign->count_teams());
1164     }
1166     /**
1167      * No active group and non group submissions allowed => 2 groups + the default one.
1168      */
1169     public function test_count_teams_non_group_allowed() {
1170         $this->resetAfterTest();
1172         $course = $this->getDataGenerator()->create_course();
1173         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1174         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1175         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1176         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1178         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1179         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1181         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1182         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1183         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1185         groups_add_member($group1, $student1);
1186         groups_add_member($group2, $student2);
1188         $assign = $this->create_instance($course, [
1189             'teamsubmission' => 1,
1190             'teamsubmissiongroupingid' => $grouping->id,
1191             'preventsubmissionnotingroup' => false,
1192         ]);
1194         $this->setUser($teacher);
1195         $this->assertEquals(3, $assign->count_teams());
1197         // Active group only.
1198         $this->assertEquals(1, $assign->count_teams($group1->id));
1199         $this->assertEquals(1, $assign->count_teams($group2->id));
1200     }
1202     /**
1203      * Active group => just selected one.
1204      */
1205     public function test_count_teams_no_active_group() {
1206         $this->resetAfterTest();
1208         $course = $this->getDataGenerator()->create_course();
1209         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1210         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1211         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1212         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1214         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1215         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1217         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1218         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1219         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1221         groups_add_member($group1, $student1);
1222         groups_add_member($group2, $student2);
1224         $assign = $this->create_instance($course, [
1225             'teamsubmission' => 1,
1226             'preventsubmissionnotingroup' => true,
1227         ]);
1229         $this->setUser($teacher);
1230         $this->assertEquals(2, $assign->count_teams());
1232         // Active group only.
1233         $this->assertEquals(1, $assign->count_teams($group1->id));
1234         $this->assertEquals(1, $assign->count_teams($group2->id));
1235     }
1237     /**
1238      * Active group => just selected one.
1239      */
1240     public function test_count_teams_groups_only() {
1241         $this->resetAfterTest();
1243         $course = $this->getDataGenerator()->create_course();
1244         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1246         $assign = $this->create_instance($course, [
1247             'teamsubmission' => 1,
1248             'teamsubmissiongroupingid' => $grouping->id,
1249             'preventsubmissionnotingroup' => false,
1250         ]);
1251         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1253         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1254         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1255         groups_add_member($group1, $student1);
1257         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1258         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1259         groups_add_member($group2, $student2);
1261         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1262         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1264         $this->setUser($teacher);
1266         $assign = $this->create_instance($course, [
1267             'teamsubmission' => 1,
1268             'preventsubmissionnotingroup' => true,
1269         ]);
1270         $this->assertEquals(2, $assign->count_teams());
1271     }
1273     public function test_submit_to_default_group() {
1274         global $DB, $SESSION;
1276         $this->resetAfterTest();
1278         $course = $this->getDataGenerator()->create_course();
1279         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1280         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1282         $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1283         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1285         $assign = $this->create_instance($course, [
1286             'teamsubmission' => 1,
1287             'assignsubmission_onlinetext_enabled' => 1,
1288             'submissiondrafts' => 0,
1289             'groupmode' => VISIBLEGROUPS,
1290         ]);
1292         $usergroup = $assign->get_submission_group($student->id);
1293         $this->assertFalse($usergroup, 'New student is in default group');
1295         // Add a submission.
1296         $this->add_submission($student, $assign);
1297         $this->submit_for_grading($student, $assign);
1299         // Set active groups to all groups.
1300         $this->setUser($teacher);
1301         $SESSION->activegroup[$course->id]['aag'][0] = 0;
1302         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1304         // Set an active group.
1305         $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id;
1306         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1307     }
1309     public function test_count_submissions_no_draft() {
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         $assign->get_user_submission($student->id, true);
1322         // Note: Drafts count as a submission.
1323         $this->assertEquals(0, $assign->count_grades());
1324         $this->assertEquals(0, $assign->count_submissions());
1325         $this->assertEquals(1, $assign->count_submissions(true));
1326         $this->assertEquals(0, $assign->count_submissions_need_grading());
1327         $this->assertEquals(1, $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(0, $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_draft() {
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);
1346         // Note: Drafts count as a submission.
1347         $this->assertEquals(0, $assign->count_grades());
1348         $this->assertEquals(1, $assign->count_submissions());
1349         $this->assertEquals(1, $assign->count_submissions(true));
1350         $this->assertEquals(0, $assign->count_submissions_need_grading());
1351         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1352         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1353         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1354         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1355     }
1357     public function test_count_submissions_submitted() {
1358         global $SESSION;
1360         $this->resetAfterTest();
1362         $course = $this->getDataGenerator()->create_course();
1363         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1364         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1366         $assign = $this->create_instance($course, [
1367             'assignsubmission_onlinetext_enabled' => 1,
1368         ]);
1370         $this->add_submission($student, $assign);
1371         $this->submit_for_grading($student, $assign);
1373         $this->assertEquals(0, $assign->count_grades());
1374         $this->assertEquals(1, $assign->count_submissions());
1375         $this->assertEquals(1, $assign->count_submissions(true));
1376         $this->assertEquals(1, $assign->count_submissions_need_grading());
1377         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1378         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1379         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1380         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1381     }
1383     public function test_count_submissions_graded() {
1384         $this->resetAfterTest();
1386         $course = $this->getDataGenerator()->create_course();
1387         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1388         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1390         $assign = $this->create_instance($course, [
1391             'assignsubmission_onlinetext_enabled' => 1,
1392         ]);
1394         $this->add_submission($student, $assign);
1395         $this->submit_for_grading($student, $assign);
1396         $this->mark_submission($teacher, $assign, $student, 50.0);
1398         // Although it has been graded, it is still marked as submitted.
1399         $this->assertEquals(1, $assign->count_grades());
1400         $this->assertEquals(1, $assign->count_submissions());
1401         $this->assertEquals(1, $assign->count_submissions(true));
1402         $this->assertEquals(0, $assign->count_submissions_need_grading());
1403         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1404         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1405         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1406         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1407     }
1409     public function test_count_submissions_graded_group() {
1410         global $SESSION;
1412         $this->resetAfterTest();
1414         $course = $this->getDataGenerator()->create_course();
1415         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1416         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1417         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1418         $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1419         groups_add_member($group, $student);
1421         $assign = $this->create_instance($course, [
1422             'assignsubmission_onlinetext_enabled' => 1,
1423             'groupmode' => VISIBLEGROUPS,
1424         ]);
1426         $this->add_submission($student, $assign);
1427         $this->submit_for_grading($student, $assign);
1429         // The user should still be listed when fetching all groups.
1430         $this->setUser($teacher);
1431         $SESSION->activegroup[$course->id]['aag'][0] = 0;
1432         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1434         // The user should still be listed when fetching just their group.
1435         $SESSION->activegroup[$course->id]['aag'][0] = $group->id;
1436         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1438         // The user should still be listed when fetching just their group.
1439         $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id;
1440         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1441     }
1443     // TODO
1444     public function x_test_count_submissions_for_team() {
1445         $this->resetAfterTest();
1447         $course = $this->getDataGenerator()->create_course();
1448         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1449         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1450         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1451         $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1452         groups_add_member($group, $student);
1454         $assign = $this->create_instance($course, [
1455             'assignsubmission_onlinetext_enabled' => 1,
1456             'teamsubmission' => 1,
1457         ]);
1459         // Add a graded submission.
1460         $this->add_submission($student, $assign);
1462         // Simulate adding a grade.
1463         $this->setUser($teacher);
1464         $data = new stdClass();
1465         $data->grade = '50.0';
1466         $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
1468         // Simulate a submission.
1469         $this->setUser($this->extrastudents[1]);
1470         $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
1471         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1472         $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1473         $data = new stdClass();
1474         $data->onlinetext_editor = array(
1475             'itemid' => file_get_unused_draft_itemid(),
1476             'text' => 'Submission text',
1477             'format' => FORMAT_MOODLE);
1478         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1479         $plugin->save($submission, $data);
1481         // Simulate a submission.
1482         $this->setUser($this->extrastudents[2]);
1483         $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
1484         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1485         $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1486         $data = new stdClass();
1487         $data->onlinetext_editor = array(
1488             'itemid' => file_get_unused_draft_itemid(),
1489             'text' => 'Submission text',
1490             'format' => FORMAT_MOODLE);
1491         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1492         $plugin->save($submission, $data);
1494         // Simulate a submission.
1495         $this->setUser($this->extrastudents[3]);
1496         $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
1497         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1498         $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1499         $data = new stdClass();
1500         $data->onlinetext_editor = array(
1501             'itemid' => file_get_unused_draft_itemid(),
1502             'text' => 'Submission text',
1503             'format' => FORMAT_MOODLE);
1504         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1505         $plugin->save($submission, $data);
1507         // Simulate adding a grade.
1508         $this->setUser($teacher);
1509         $data = new stdClass();
1510         $data->grade = '50.0';
1511         $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1512         $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1514         // Create a new submission with status NEW.
1515         $this->setUser($this->extrastudents[4]);
1516         $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
1518         $this->assertEquals(2, $assign->count_grades());
1519         $this->assertEquals(4, $assign->count_submissions());
1520         $this->assertEquals(5, $assign->count_submissions(true));
1521         $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1522         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1523     }
1525     public function test_get_grading_userid_list_only_active() {
1526         $this->resetAfterTest();
1528         $course = $this->getDataGenerator()->create_course();
1529         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1530         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1531         $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1532             $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1534         $this->setUser($teacher);
1536         $assign = $this->create_instance($course);
1537         $this->assertCount(1, $assign->testable_get_grading_userid_list());
1538     }
1540     public function test_get_grading_userid_list_all() {
1541         $this->resetAfterTest();
1543         $course = $this->getDataGenerator()->create_course();
1544         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1545         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1546         $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1547             $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1549         $this->setUser($teacher);
1550         set_user_preference('grade_report_showonlyactiveenrol', false);
1552         $assign = $this->create_instance($course);
1553         $this->assertCount(2, $assign->testable_get_grading_userid_list());
1554     }
1556     public function test_cron() {
1557         global $PAGE;
1558         $this->resetAfterTest();
1560         // First run cron so there are no messages waiting to be sent (from other tests).
1561         cron_setup_user();
1562         assign::cron();
1564         $course = $this->getDataGenerator()->create_course();
1565         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1566         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1568         // Now create an assignment and add some feedback.
1569         $this->setUser($teacher);
1570         $assign = $this->create_instance($course, [
1571             'sendstudentnotifications' => 1,
1572         ]);
1574         $this->add_submission($student, $assign);
1575         $this->submit_for_grading($student, $assign);
1576         $this->mark_submission($teacher, $assign, $student, 50.0);
1578         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1579         cron_setup_user();
1580         $sink = $this->redirectMessages();
1581         assign::cron();
1582         $messages = $sink->get_messages();
1584         $this->assertEquals(1, count($messages));
1585         $this->assertEquals(1, $messages[0]->notification);
1586         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1587         // Test customdata.
1588         $customdata = json_decode($messages[0]->customdata);
1589         $this->assertEquals($assign->get_course_module()->id, $customdata->cmid);
1590         $this->assertEquals($assign->get_instance()->id, $customdata->instance);
1591         $this->assertEquals('feedbackavailable', $customdata->messagetype);
1592         $userpicture = new user_picture($teacher);
1593         $userpicture->size = 1; // Use f1 size.
1594         $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
1595         $this->assertEquals(0, $customdata->uniqueidforuser);   // Not used in this case.
1596         $this->assertFalse($customdata->blindmarking);
1597     }
1599     public function test_cron_without_notifications() {
1600         $this->resetAfterTest();
1602         // First run cron so there are no messages waiting to be sent (from other tests).
1603         cron_setup_user();
1604         assign::cron();
1606         $course = $this->getDataGenerator()->create_course();
1607         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1608         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1610         // Now create an assignment and add some feedback.
1611         $this->setUser($teacher);
1612         $assign = $this->create_instance($course, [
1613             'sendstudentnotifications' => 1,
1614         ]);
1616         $this->add_submission($student, $assign);
1617         $this->submit_for_grading($student, $assign);
1618         $this->mark_submission($teacher, $assign, $student, 50.0, [
1619             'sendstudentnotifications' => 0,
1620         ]);
1622         cron_setup_user();
1623         $sink = $this->redirectMessages();
1624         assign::cron();
1625         $messages = $sink->get_messages();
1627         $this->assertEquals(0, count($messages));
1628     }
1630     public function test_cron_regraded() {
1631         $this->resetAfterTest();
1633         // First run cron so there are no messages waiting to be sent (from other tests).
1634         cron_setup_user();
1635         assign::cron();
1637         $course = $this->getDataGenerator()->create_course();
1638         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1639         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1641         // Now create an assignment and add some feedback.
1642         $this->setUser($teacher);
1643         $assign = $this->create_instance($course, [
1644             'sendstudentnotifications' => 1,
1645         ]);
1647         $this->add_submission($student, $assign);
1648         $this->submit_for_grading($student, $assign);
1649         $this->mark_submission($teacher, $assign, $student, 50.0);
1651         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1652         cron_setup_user();
1653         assign::cron();
1655         // Regrade.
1656         $this->mark_submission($teacher, $assign, $student, 50.0);
1658         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1659         cron_setup_user();
1660         $sink = $this->redirectMessages();
1661         assign::cron();
1662         $messages = $sink->get_messages();
1664         $this->assertEquals(1, count($messages));
1665         $this->assertEquals(1, $messages[0]->notification);
1666         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1667     }
1669     /**
1670      * Test delivery of grade notifications as controlled by marking workflow.
1671      */
1672     public function test_markingworkflow_cron() {
1673         $this->resetAfterTest();
1675         // First run cron so there are no messages waiting to be sent (from other tests).
1676         cron_setup_user();
1677         assign::cron();
1679         $course = $this->getDataGenerator()->create_course();
1680         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1681         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1683         // Now create an assignment and add some feedback.
1684         $this->setUser($teacher);
1685         $assign = $this->create_instance($course, [
1686             'sendstudentnotifications' => 1,
1687             'markingworkflow' => 1,
1688         ]);
1690         // Mark a submission but set the workflowstate to an unreleased state.
1691         // This should not trigger a notification.
1692         $this->add_submission($student, $assign);
1693         $this->submit_for_grading($student, $assign);
1694         $this->mark_submission($teacher, $assign, $student, 50.0, [
1695             'sendstudentnotifications' => 1,
1696             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1697         ]);
1699         cron_setup_user();
1700         $sink = $this->redirectMessages();
1701         assign::cron();
1702         $messages = $sink->get_messages();
1704         $this->assertEquals(0, count($messages));
1706         // Transition to the released state.
1707         $this->setUser($teacher);
1708         $submission = $assign->get_user_submission($student->id, true);
1709         $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1710         $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1712         // Now run cron and see that one message was sent.
1713         cron_setup_user();
1714         $sink = $this->redirectMessages();
1715         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1716         assign::cron();
1717         $messages = $sink->get_messages();
1719         $this->assertEquals(1, count($messages));
1720         $this->assertEquals(1, $messages[0]->notification);
1721         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1722     }
1724     public function test_cron_message_includes_courseid() {
1725         $this->resetAfterTest();
1727         // First run cron so there are no messages waiting to be sent (from other tests).
1728         cron_setup_user();
1729         assign::cron();
1731         $course = $this->getDataGenerator()->create_course();
1732         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1733         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1735         // Now create an assignment and add some feedback.
1736         $this->setUser($teacher);
1737         $assign = $this->create_instance($course, [
1738             'sendstudentnotifications' => 1,
1739         ]);
1741         // Mark a submission but set the workflowstate to an unreleased state.
1742         // This should not trigger a notification.
1743         $this->add_submission($student, $assign);
1744         $this->submit_for_grading($student, $assign);
1745         $this->mark_submission($teacher, $assign, $student);
1746         phpunit_util::stop_message_redirection();
1748         // Now run cron and see that one message was sent.
1749         cron_setup_user();
1750         $this->preventResetByRollback();
1751         $sink = $this->redirectEvents();
1752         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1753         assign::cron();
1755         $events = $sink->get_events();
1756         $event = reset($events);
1757         $this->assertInstanceOf('\core\event\notification_sent', $event);
1758         $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1759         $sink->close();
1760     }
1762     public function test_is_graded() {
1763         $this->resetAfterTest();
1765         $course = $this->getDataGenerator()->create_course();
1766         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1767         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1768         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1770         $assign = $this->create_instance($course);
1772         $this->add_submission($student, $assign);
1773         $this->submit_for_grading($student, $assign);
1774         $this->mark_submission($teacher, $assign, $student, 50.0);
1776         $this->setUser($teacher);
1777         $this->assertEquals(true, $assign->testable_is_graded($student->id));
1778         $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1779     }
1781     public function test_can_grade() {
1782         global $DB;
1784         $this->resetAfterTest();
1786         $course = $this->getDataGenerator()->create_course();
1787         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1788         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1790         $assign = $this->create_instance($course);
1792         $this->setUser($student);
1793         $this->assertEquals(false, $assign->can_grade());
1795         $this->setUser($teacher);
1796         $this->assertEquals(true, $assign->can_grade());
1798         // Test the viewgrades capability for other users.
1799         $this->setUser();
1800         $this->assertTrue($assign->can_grade($teacher->id));
1801         $this->assertFalse($assign->can_grade($student->id));
1803         // Test the viewgrades capability - without mod/assign:grade.
1804         $this->setUser($student);
1806         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1807         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1808         $this->assertEquals(false, $assign->can_grade());
1809     }
1811     public function test_can_view_submission() {
1812         global $DB;
1814         $this->resetAfterTest();
1816         $course = $this->getDataGenerator()->create_course();
1817         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1818         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1819         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1820         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1821         $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1822             $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1824         $assign = $this->create_instance($course);
1826         $this->setUser($student);
1827         $this->assertEquals(true, $assign->can_view_submission($student->id));
1828         $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1829         $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1831         $this->setUser($teacher);
1832         $this->assertEquals(true, $assign->can_view_submission($student->id));
1833         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1834         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1835         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1837         $this->setUser($editingteacher);
1838         $this->assertEquals(true, $assign->can_view_submission($student->id));
1839         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1840         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1841         $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1843         // Test the viewgrades capability - without mod/assign:grade.
1844         $this->setUser($student);
1845         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1846         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1847         $this->assertEquals(true, $assign->can_view_submission($student->id));
1848         $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1849         $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1850         $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1851     }
1853     public function test_update_submission() {
1854         $this->resetAfterTest();
1856         $course = $this->getDataGenerator()->create_course();
1857         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1858         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1860         $assign = $this->create_instance($course);
1862         $this->add_submission($student, $assign);
1863         $submission = $assign->get_user_submission($student->id, 0);
1864         $assign->testable_update_submission($submission, $student->id, true, true);
1866         $this->setUser($teacher);
1868         // Verify the gradebook update.
1869         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1871         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1872         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1873     }
1875     public function test_update_submission_team() {
1876         $this->resetAfterTest();
1878         $course = $this->getDataGenerator()->create_course();
1879         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1880         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1882         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1883         groups_add_member($group, $student);
1885         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1886         groups_add_member($group, $otherstudent);
1888         $assign = $this->create_instance($course, [
1889             'teamsubmission' => 1,
1890         ]);
1892         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1893         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1894         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1896         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1897         $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1898         $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1900         $this->add_submission($student, $assign);
1901         $submission = $assign->get_group_submission($student->id, 0, true);
1902         $assign->testable_update_submission($submission, $student->id, true, true);
1904         // Verify the gradebook update for the student.
1905         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1907         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1908         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1910         // Verify the gradebook update for the other student.
1911         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1913         $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1914         $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1915     }
1917     public function test_update_submission_suspended() {
1918         $this->resetAfterTest();
1920         $course = $this->getDataGenerator()->create_course();
1921         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1922         $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1924         $assign = $this->create_instance($course);
1926         $this->add_submission($student, $assign);
1927         $submission = $assign->get_user_submission($student->id, 0);
1928         $assign->testable_update_submission($submission, $student->id, true, false);
1930         $this->setUser($teacher);
1932         // Verify the gradebook update.
1933         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1935         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1936         $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1937     }
1939     public function test_update_submission_blind() {
1940         $this->resetAfterTest();
1942         $course = $this->getDataGenerator()->create_course();
1943         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1944         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1946         $assign = $this->create_instance($course, [
1947             'blindmarking' => 1,
1948         ]);
1950         $this->add_submission($student, $assign);
1951         $submission = $assign->get_user_submission($student->id, 0);
1952         $assign->testable_update_submission($submission, $student->id, true, false);
1954         // Verify the gradebook update.
1955         $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1957         // The usermodified is not set because this is blind marked.
1958         $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1959         $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1960     }
1962     public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
1963         global $PAGE;
1965         $this->resetAfterTest();
1967         $course = $this->getDataGenerator()->create_course();
1968         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1969         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1971         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1972         groups_add_member($group, $student);
1974         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1975         groups_add_member($group, $otherstudent);
1977         $assign = $this->create_instance($course, [
1978             'teamsubmission' => 1,
1979             'assignsubmission_onlinetext_enabled' => 1,
1980             'submissiondrafts' => 1,
1981             'requireallteammemberssubmit' => 1,
1982         ]);
1984         // Now verify group assignments.
1985         $this->setUser($teacher);
1986         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1988         // Add a submission.
1989         $this->add_submission($student, $assign);
1991         // Check we can see the submit button.
1992         $this->setUser($student);
1993         $output = $assign->view_student_summary($student, true);
1994         $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1996         $submission = $assign->get_group_submission($student->id, 0, true);
1997         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1998         $assign->testable_update_submission($submission, $student->id, true, true);
2000         // Check that the student does not see "Submit" button.
2001         $output = $assign->view_student_summary($student, true);
2002         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2004         // Change to another user in the same group.
2005         $this->setUser($otherstudent);
2006         $output = $assign->view_student_summary($otherstudent, true);
2007         $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
2009         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2010         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2011         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2012         $output = $assign->view_student_summary($otherstudent, true);
2013         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2014     }
2016     public function test_group_submissions_submit_for_marking() {
2017         global $PAGE;
2019         $this->resetAfterTest();
2021         $course = $this->getDataGenerator()->create_course();
2022         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2023         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2025         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2026         groups_add_member($group, $student);
2028         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2029         groups_add_member($group, $otherstudent);
2031         // Now verify group assignments.
2032         $this->setUser($teacher);
2033         $time = time();
2034         $assign = $this->create_instance($course, [
2035             'teamsubmission' => 1,
2036             'assignsubmission_onlinetext_enabled' => 1,
2037             'submissiondrafts' => 1,
2038             'requireallteammemberssubmit' => 0,
2039             'duedate' => $time - (2 * DAYSECS),
2040         ]);
2041         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2043         // Add a submission.
2044         $this->add_submission($student, $assign);
2046         // Check we can see the submit button.
2047         $output = $assign->view_student_summary($student, true);
2048         $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
2049         $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2050         $difftime = time() - $time;
2051         $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2053         $submission = $assign->get_group_submission($student->id, 0, true);
2054         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2055         $assign->testable_update_submission($submission, $student->id, true, true);
2057         // Check that the student does not see "Submit" button.
2058         $output = $assign->view_student_summary($student, true);
2059         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2061         // Change to another user in the same group.
2062         $this->setUser($otherstudent);
2063         $output = $assign->view_student_summary($otherstudent, true);
2064         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2066         // Check that time remaining is not overdue.
2067         $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2068         $difftime = time() - $time;
2069         $this->assertStringContainsString(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2071         $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2072         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2073         $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2074         $output = $assign->view_student_summary($otherstudent, true);
2075         $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2076     }
2078     public function test_submissions_open() {
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');
2087         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2088         $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
2089             $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
2091         $this->setAdminUser();
2093         $now = time();
2094         $tomorrow = $now + DAYSECS;
2095         $oneweek = $now + WEEKSECS;
2096         $yesterday = $now - DAYSECS;
2098         $assign = $this->create_instance($course);
2099         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2101         $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
2102         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2104         $assign = $this->create_instance($course, ['duedate' => $yesterday]);
2105         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2107         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
2108         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2110         $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2111         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2113         $assign->testable_save_user_extension($student->id, $tomorrow);
2114         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2116         $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2117         $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2119         $this->setUser($student);
2120         $submission = $assign->get_user_submission($student->id, true);
2121         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2122         $assign->testable_update_submission($submission, $student->id, true, false);
2124         $this->setUser($teacher);
2125         $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2126     }
2128     public function test_get_graders() {
2129         global $DB;
2131         $this->resetAfterTest();
2133         $course = $this->getDataGenerator()->create_course();
2134         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2135         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2136         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2138         $this->setAdminUser();
2140         // Create an assignment with no groups.
2141         $assign = $this->create_instance($course);
2142         $this->assertCount(2, $assign->testable_get_graders($student->id));
2143     }
2145     public function test_get_graders_separate_groups() {
2146         global $DB;
2148         $this->resetAfterTest();
2150         $course = $this->getDataGenerator()->create_course();
2151         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2152         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2153         $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2154         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2155         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2156         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2158         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2159         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2160         groups_add_member($group1, $student);
2162         $this->setAdminUser();
2164         // Force create an assignment with SEPARATEGROUPS.
2165         $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2166         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2168         $assign = $this->create_instance($course, [
2169             'groupingid' => $grouping->id,
2170             'groupmode' => SEPARATEGROUPS,
2171         ]);
2173         $this->assertCount(4, $assign->testable_get_graders($student->id));
2175         // Note the second student is in a group that is not in the grouping.
2176         // This means that we get all graders that are not in a group in the grouping.
2177         $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2178     }
2180     public function test_get_notified_users() {
2181         global $CFG, $DB;
2183         $this->resetAfterTest();
2185         $course = $this->getDataGenerator()->create_course();
2186         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2187         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2188         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2190         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2191         groups_add_member($group1, $teacher);
2193         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2194         groups_add_member($group1, $editingteacher);
2196         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2197         groups_add_member($group1, $student);
2199         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2200         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2202         $capability = 'mod/assign:receivegradernotifications';
2203         $coursecontext = context_course::instance($course->id);
2204         $role = $DB->get_record('role', array('shortname' => 'teacher'));
2206         $this->setUser($teacher);
2208         // Create an assignment with no groups.
2209         $assign = $this->create_instance($course);
2211         $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2213         // Change nonediting teachers role to not receive grader notifications.
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         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2222     }
2224     public function test_get_notified_users_in_grouping() {
2225         global $CFG, $DB;
2227         $this->resetAfterTest();
2229         $course = $this->getDataGenerator()->create_course();
2230         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2231         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2232         $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2234         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2235         groups_add_member($group1, $teacher);
2237         $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2238         groups_add_member($group1, $editingteacher);
2240         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2241         groups_add_member($group1, $student);
2243         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2244         $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2246         // Force create an assignment with SEPARATEGROUPS.
2247         $assign = $this->create_instance($course, [
2248             'groupingid' => $grouping->id,
2249             'groupmode' => SEPARATEGROUPS,
2250         ]);
2252         // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2253         $this->setUser($student);
2254         $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2256         // Note the second student is in a group that is not in the grouping.
2257         // This means that we get all graders that are not in a group in the grouping.
2258         $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2260         // Change nonediting teachers role to not receive grader notifications.
2261         $capability = 'mod/assign:receivegradernotifications';
2262         $coursecontext = context_course::instance($course->id);
2263         $role = $DB->get_record('role', ['shortname' => 'teacher']);
2264         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2266         // Only the editing teachers will be returned.
2267         $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2269         // Note the second student is in a group that is not in the grouping.
2270         // This means that we get all graders that are not in a group in the grouping.
2271         // Unfortunately there are no editing teachers who are not in a group.
2272         $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2273     }
2275     public function test_group_members_only() {
2276         global $CFG;
2278         $this->resetAfterTest();
2280         $course = $this->getDataGenerator()->create_course();
2281         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2282         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2283         $this->getDataGenerator()->create_grouping_group([
2284             'groupid' => $group1->id,
2285             'groupingid' => $grouping->id,
2286         ]);
2288         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2289         $this->getDataGenerator()->create_grouping_group([
2290             'groupid' => $group2->id,
2291             'groupingid' => $grouping->id,
2292         ]);
2294         $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2296         // Add users in the following groups
2297         // - Teacher - Group 1.
2298         // - Student - Group 1.
2299         // - Student - Group 2.
2300         // - Student - Unrelated Group
2301         // - Student - No group.
2302         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2303         groups_add_member($group1, $teacher);
2305         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2306         groups_add_member($group1, $student);
2308         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2309         groups_add_member($group2, $otherstudent);
2311         $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2312         groups_add_member($group2, $otherstudent);
2314         $this->getDataGenerator()->create_and_enrol($course, 'student');
2316         $this->setAdminUser();
2318         $CFG->enableavailability = true;
2319         $assign = $this->create_instance($course, [], [
2320             'availability' => json_encode(
2321                 \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2322             ),
2323             'groupingid' => $grouping->id,
2324         ]);
2326         // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2327         // group, or the student in an unrelated group.
2328         $this->setUser($teacher);
2329         $participants = $assign->list_participants(0, true);
2330         $this->assertCount(2, $participants);
2331         $this->assertTrue(isset($participants[$student->id]));
2332         $this->assertTrue(isset($participants[$otherstudent->id]));
2333     }
2335     public function test_get_uniqueid_for_user() {
2336         $this->resetAfterTest();
2338         $course = $this->getDataGenerator()->create_course();
2339         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2340         $students = [];
2341         for ($i = 0; $i < 10; $i++) {
2342             $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2343             $students[$student->id] = $student;
2344         }
2346         $this->setUser($teacher);
2347         $assign = $this->create_instance($course);
2349         foreach ($students as $student) {
2350             $uniqueid = $assign->get_uniqueid_for_user($student->id);
2351             $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2352         }
2353     }
2355     public function test_show_student_summary() {
2356         global $CFG, $PAGE;
2358         $this->resetAfterTest();
2360         $course = $this->getDataGenerator()->create_course();
2361         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2362         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2363         $this->setUser($teacher);
2364         $assign = $this->create_instance($course);
2365         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2367         // No feedback should be available because this student has not been graded.
2368         $this->setUser($student);
2369         $output = $assign->view_student_summary($student, true);
2370         $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if there is no grade');
2372         // Simulate adding a grade.
2373         $this->add_submission($student, $assign);
2374         $this->submit_for_grading($student, $assign);
2375         $this->mark_submission($teacher, $assign, $student);
2377         // Now we should see the feedback.
2378         $this->setUser($student);
2379         $output = $assign->view_student_summary($student, true);
2380         $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
2382         // Now hide the grade in gradebook.
2383         $this->setUser($teacher);
2384         require_once($CFG->libdir.'/gradelib.php');
2385         $gradeitem = new grade_item(array(
2386             'itemtype'      => 'mod',
2387             'itemmodule'    => 'assign',
2388             'iteminstance'  => $assign->get_instance()->id,
2389             'courseid'      => $course->id));
2391         $gradeitem->set_hidden(1, false);
2393         // No feedback should be available because the grade is hidden.
2394         $this->setUser($student);
2395         $output = $assign->view_student_summary($student, true);
2396         $this->assertDoesNotMatchRegularExpression('/Feedback/', $output,
2397             'Do not show feedback if the grade is hidden in the gradebook');
2399         // Freeze the context.
2400         $this->setAdminUser();
2401         $context = $assign->get_context();
2402         $CFG->contextlocking = true;
2403         $context->set_locked(true);
2405         // No feedback should be available because the grade is hidden.
2406         $this->setUser($student);
2407         $output = $assign->view_student_summary($student, true);
2408         $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2410         // Show the feedback again - it should still be visible even in a frozen context.
2411         $this->setUser($teacher);
2412         $gradeitem->set_hidden(0, false);
2414         $this->setUser($student);
2415         $output = $assign->view_student_summary($student, true);
2416         $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
2417     }
2419     public function test_show_student_summary_with_feedback() {
2420         global $CFG, $PAGE;
2422         $this->resetAfterTest();
2424         $course = $this->getDataGenerator()->create_course();
2425         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2426         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2427         $this->setUser($teacher);
2428         $assign = $this->create_instance($course, [
2429             'assignfeedback_comments_enabled' => 1
2430         ]);
2431         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2433         // No feedback should be available because this student has not been graded.
2434         $this->setUser($student);
2435         $output = $assign->view_student_summary($student, true);
2436         $this->assertDoesNotMatchRegularExpression('/Feedback/', $output);
2438         // Simulate adding a grade.
2439         $this->add_submission($student, $assign);
2440         $this->submit_for_grading($student, $assign);
2441         $this->mark_submission($teacher, $assign, $student, null, [
2442             'assignfeedbackcomments_editor' => [
2443                 'text' => 'Tomato sauce',
2444                 'format' => FORMAT_MOODLE,
2445             ],
2446         ]);
2448         // Should have feedback but no grade.
2449         $this->setUser($student);
2450         $output = $assign->view_student_summary($student, true);
2451         $this->assertMatchesRegularExpression('/Feedback/', $output);
2452         $this->assertMatchesRegularExpression('/Tomato sauce/', $output);
2453         $this->assertDoesNotMatchRegularExpression('/Grade/', $output, 'Do not show grade when there is no grade.');
2454         $this->assertDoesNotMatchRegularExpression('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2456         // Add a grade now.
2457         $this->mark_submission($teacher, $assign, $student, 50.0, [
2458             'assignfeedbackcomments_editor' => [
2459                 'text' => 'Bechamel sauce',
2460                 'format' => FORMAT_MOODLE,
2461             ],
2462         ]);
2464         // Should have feedback but no grade.
2465         $this->setUser($student);
2466         $output = $assign->view_student_summary($student, true);
2467         $this->assertDoesNotMatchRegularExpression('/Tomato sauce/', $output);
2468         $this->assertMatchesRegularExpression('/Bechamel sauce/', $output);
2469         $this->assertMatchesRegularExpression('/Grade/', $output);
2470         $this->assertMatchesRegularExpression('/Graded on/', $output);
2472         // Now hide the grade in gradebook.
2473         $this->setUser($teacher);
2474         $gradeitem = new grade_item(array(
2475             'itemtype'      => 'mod',
2476             'itemmodule'    => 'assign',
2477             'iteminstance'  => $assign->get_instance()->id,
2478             'courseid'      => $course->id));
2480         $gradeitem->set_hidden(1, false);
2482         // No feedback should be available because the grade is hidden.
2483         $this->setUser($student);
2484         $output = $assign->view_student_summary($student, true);
2485         $this->assertDoesNotMatchRegularExpression('/Feedback/', $output,
2486             'Do not show feedback if the grade is hidden in the gradebook');
2487     }
2489     /**
2490      * Test reopen behavior when in "Manual" mode.
2491      */
2492     public function test_attempt_reopen_method_manual() {
2493         global $PAGE;
2495         $this->resetAfterTest();
2496         $course = $this->getDataGenerator()->create_course();
2497         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2498         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2500         $assign = $this->create_instance($course, [
2501             'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2502             'maxattempts' => 3,
2503             'submissiondrafts' => 1,
2504             'assignsubmission_onlinetext_enabled' => 1,
2505         ]);
2506         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2508         // Student should be able to see an add submission button.
2509         $this->setUser($student);
2510         $output = $assign->view_student_summary($student, true);
2511         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2513         // Add a submission.
2514         $this->add_submission($student, $assign);
2515         $this->submit_for_grading($student, $assign);
2517         // Verify the student cannot make changes to the submission.
2518         $output = $assign->view_student_summary($student, true);
2519         $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2521         // Mark the submission.
2522         $this->mark_submission($teacher, $assign, $student);
2524         // Check the student can see the grade.
2525         $this->setUser($student);
2526         $output = $assign->view_student_summary($student, true);
2527         $this->assertNotEquals(false, strpos($output, '50.0'));
2529         // Allow the student another attempt.
2530         $teacher->ignoresesskey = true;
2531         $this->setUser($teacher);
2532         $result = $assign->testable_process_add_attempt($student->id);
2533         $this->assertEquals(true, $result);
2535         // Check that the previous attempt is now in the submission history table.
2536         $this->setUser($student);
2537         $output = $assign->view_student_summary($student, true);
2538         // Need a better check.
2539         $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2541         // Check that the student now has a button for Add a new attempt".
2542         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2543         // Check that the student now does not have a button for Submit.
2544         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2546         // Check that the student now has a submission history.
2547         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2549         $this->setUser($teacher);
2550         // Check that the grading table loads correctly and contains this user.
2551         // This is also testing that we do not get duplicate rows in the grading table.
2552         $gradingtable = new assign_grading_table($assign, 100, '', 0, true);
2553         $output = $assign->get_renderer()->render($gradingtable);
2554         $this->assertEquals(true, strpos($output, $student->lastname));
2556         // Should be 1 not 2.
2557         $this->assertEquals(1, $assign->count_submissions());
2558         $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2559         $this->assertEquals(0, $assign->count_submissions_need_grading());
2560         $this->assertEquals(1, $assign->count_grades());
2562         // Change max attempts to unlimited.
2563         $formdata = clone($assign->get_instance());
2564         $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2565         $formdata->instance = $formdata->id;
2566         $assign->update_instance($formdata);
2568         // Mark the submission again.
2569         $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2571         // Check the grade exists.
2572         $this->setUser($teacher);
2573         $grades = $assign->get_user_grades_for_gradebook($student->id);
2574         $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2576         // Check we can reopen still.
2577         $result = $assign->testable_process_add_attempt($student->id);
2578         $this->assertEquals(true, $result);
2580         // Should no longer have a grade because there is no grade for the latest attempt.
2581         $grades = $assign->get_user_grades_for_gradebook($student->id);
2582         $this->assertEmpty($grades);
2583     }
2585     /**
2586      * Test reopen behavior when in "Reopen until pass" mode.
2587      */
2588     public function test_attempt_reopen_method_untilpass() {
2589         global $PAGE;
2591         $this->resetAfterTest();
2592         $course = $this->getDataGenerator()->create_course();
2593         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2594         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2596         $assign = $this->create_instance($course, [
2597             'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2598             'maxattempts' => 3,
2599             'submissiondrafts' => 1,
2600             'assignsubmission_onlinetext_enabled' => 1,
2601         ]);
2602         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2604         // Set grade to pass to 80.
2605         $gradeitem = $assign->get_grade_item();
2606         $gradeitem->gradepass = '80.0';
2607         $gradeitem->update();
2609         // Student should be able to see an add submission button.
2610         $this->setUser($student);
2611         $output = $assign->view_student_summary($student, true);
2612         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2614         // Add a submission.
2615         $this->add_submission($student, $assign);
2616         $this->submit_for_grading($student, $assign);
2618         // Verify the student cannot make a new attempt.
2619         $output = $assign->view_student_summary($student, true);
2620         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2622         // Mark the submission as non-passing.
2623         $this->mark_submission($teacher, $assign, $student, 50.0);
2625         // Check the student can see the grade.
2626         $this->setUser($student);
2627         $output = $assign->view_student_summary($student, true);
2628         $this->assertNotEquals(false, strpos($output, '50.0'));
2630         // Check that the student now has a button for Add a new attempt.
2631         $output = $assign->view_student_summary($student, true);
2632         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2634         // Check that the student now does not have a button for Submit.
2635         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2637         // Check that the student now has a submission history.
2638         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2640         // Add a second submission.
2641         $this->add_submission($student, $assign);
2642         $this->submit_for_grading($student, $assign);
2644         // Mark the submission as passing.
2645         $this->mark_submission($teacher, $assign, $student, 80.0);
2647         // Check that the student does not have a button for Add a new attempt.
2648         $this->setUser($student);
2649         $output = $assign->view_student_summary($student, true);
2650         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2652         // Re-mark the submission as not passing.
2653         $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2655         // Check that the student now has a button for Add a new attempt.
2656         $this->setUser($student);
2657         $output = $assign->view_student_summary($student, true);
2658         $this->assertMatchesRegularExpression('/' . get_string('addnewattempt', 'assign') . '/', $output);
2659         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2660     }
2662     public function test_attempt_reopen_method_untilpass_passing() {
2663         global $PAGE;
2665         $this->resetAfterTest();
2666         $course = $this->getDataGenerator()->create_course();
2667         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2668         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2670         $assign = $this->create_instance($course, [
2671             'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2672             'maxattempts' => 3,
2673             'submissiondrafts' => 1,
2674             'assignsubmission_onlinetext_enabled' => 1,
2675         ]);
2676         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2678         // Set grade to pass to 80.
2679         $gradeitem = $assign->get_grade_item();
2680         $gradeitem->gradepass = '80.0';
2681         $gradeitem->update();
2683         // Student should be able to see an add submission button.
2684         $this->setUser($student);
2685         $output = $assign->view_student_summary($student, true);
2686         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2688         // Add a submission as a student.
2689         $this->add_submission($student, $assign);
2690         $this->submit_for_grading($student, $assign);
2692         // Mark the submission as passing.
2693         $this->mark_submission($teacher, $assign, $student, 100.0);
2695         // Check the student can see the grade.
2696         $this->setUser($student);
2697         $output = $assign->view_student_summary($student, true);
2698         $this->assertNotEquals(false, strpos($output, '100.0'));
2700         // Check that the student does not have a button for Add a new attempt.
2701         $output = $assign->view_student_summary($student, true);
2702         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2703     }
2705     public function test_attempt_reopen_method_untilpass_no_passing_requirement() {
2706         global $PAGE;
2708         $this->resetAfterTest();
2709         $course = $this->getDataGenerator()->create_course();
2710         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2711         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2713         $assign = $this->create_instance($course, [
2714             'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2715             'maxattempts' => 3,
2716             'submissiondrafts' => 1,
2717             'assignsubmission_onlinetext_enabled' => 1,
2718         ]);
2719         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2721         // Set grade to pass to 0, so that no attempts should reopen.
2722         $gradeitem = $assign->get_grade_item();
2723         $gradeitem->gradepass = '0';
2724         $gradeitem->update();
2726         // Student should be able to see an add submission button.
2727         $this->setUser($student);
2728         $output = $assign->view_student_summary($student, true);
2729         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2731         // Add a submission.
2732         $this->add_submission($student, $assign);
2733         $this->submit_for_grading($student, $assign);
2735         // Mark the submission with any grade.
2736         $this->mark_submission($teacher, $assign, $student, 0.0);
2738         // Check the student can see the grade.
2739         $this->setUser($student);
2740         $output = $assign->view_student_summary($student, true);
2741         $this->assertNotEquals(false, strpos($output, '0.0'));
2743         // Check that the student does not have a button for Add a new attempt.
2744         $output = $assign->view_student_summary($student, true);
2745         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2746     }
2748     /**
2749      * Test student visibility for each stage of the marking workflow.
2750      */
2751     public function test_markingworkflow() {
2752         global $PAGE;
2754         $this->resetAfterTest();
2755         $course = $this->getDataGenerator()->create_course();
2756         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2757         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2759         $assign = $this->create_instance($course, [
2760             'markingworkflow' => 1,
2761         ]);
2763         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2765         // Mark the submission and set to notmarked.
2766         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2767             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2768         ]);
2770         // Check the student can't see the grade.
2771         $this->setUser($student);
2772         $output = $assign->view_student_summary($student, true);
2773         $this->assertEquals(false, strpos($output, '50.0'));
2775         // Make sure the grade isn't pushed to the gradebook.
2776         $grades = $assign->get_user_grades_for_gradebook($student->id);
2777         $this->assertEmpty($grades);
2779         // Mark the submission and set to inmarking.
2780         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2781             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2782         ]);
2784         // Check the student can't see the grade.
2785         $this->setUser($student);
2786         $output = $assign->view_student_summary($student, true);
2787         $this->assertEquals(false, strpos($output, '50.0'));
2789         // Make sure the grade isn't pushed to the gradebook.
2790         $grades = $assign->get_user_grades_for_gradebook($student->id);
2791         $this->assertEmpty($grades);
2793         // Mark the submission and set to readyforreview.
2794         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2795             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2796         ]);
2798         // Check the student can't see the grade.
2799         $this->setUser($student);
2800         $output = $assign->view_student_summary($student, true);
2801         $this->assertEquals(false, strpos($output, '50.0'));
2803         // Make sure the grade isn't pushed to the gradebook.
2804         $grades = $assign->get_user_grades_for_gradebook($student->id);
2805         $this->assertEmpty($grades);
2807         // Mark the submission and set to inreview.
2808         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2809             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2810         ]);
2812         // Check the student can't see the grade.
2813         $this->setUser($student);
2814         $output = $assign->view_student_summary($student, true);
2815         $this->assertEquals(false, strpos($output, '50.0'));
2817         // Make sure the grade isn't pushed to the gradebook.
2818         $grades = $assign->get_user_grades_for_gradebook($student->id);
2819         $this->assertEmpty($grades);
2821         // Mark the submission and set to readyforrelease.
2822         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2823             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
2824         ]);
2826         // Check the student can't see the grade.
2827         $this->setUser($student);
2828         $output = $assign->view_student_summary($student, true);
2829         $this->assertEquals(false, strpos($output, '50.0'));
2831         // Make sure the grade isn't pushed to the gradebook.
2832         $grades = $assign->get_user_grades_for_gradebook($student->id);
2833         $this->assertEmpty($grades);
2835         // Mark the submission and set to released.
2836         $this->mark_submission($teacher, $assign, $student, 50.0,  [
2837             'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
2838         ]);
2840         // Check the student can see the grade.
2841         $this->setUser($student);
2842         $output = $assign->view_student_summary($student, true);
2843         $this->assertNotEquals(false, strpos($output, '50.0'));
2845         // Make sure the grade is pushed to the gradebook.
2846         $grades = $assign->get_user_grades_for_gradebook($student->id);
2847         $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
2848     }
2850     /**
2851      * Test that a student allocated a specific marker is only shown to that marker.
2852      */
2853     public function test_markerallocation() {
2854         global $PAGE;
2856         $this->resetAfterTest();
2857         $course = $this->getDataGenerator()->create_course();
2858         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2859         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2860         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2862         $assign = $this->create_instance($course, [
2863             'markingworkflow' => 1,
2864             'markingallocation' => 1
2865         ]);
2867         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2869         // Allocate marker to submission.
2870         $this->mark_submission($teacher, $assign, $student, null, [
2871             'allocatedmarker' => $teacher->id,
2872         ]);
2874         // Check the allocated marker can view the submission.
2875         $this->setUser($teacher);
2876         $users = $assign->list_participants(0, true);
2877         $this->assertEquals(1, count($users));
2878         $this->assertTrue(isset($users[$student->id]));
2880         $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2881         $context = context_module::instance($cm->id);
2882         $assign = new mod_assign_testable_assign($context, $cm, $course);
2884         // Check that other teachers can't view this submission.
2885         $this->setUser($otherteacher);
2886         $users = $assign->list_participants(0, true);
2887         $this->assertEquals(0, count($users));
2888     }
2890     /**
2891      * Ensure that a teacher cannot submit for students as standard.
2892      */
2893     public function test_teacher_submit_for_student() {
2894         global $PAGE;
2896         $this->resetAfterTest();
2897         $course = $this->getDataGenerator()->create_course();
2898         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2899         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2901         $assign = $this->create_instance($course, [
2902             'assignsubmission_onlinetext_enabled' => 1,
2903             'submissiondrafts' => 1,
2904         ]);
2906         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2908         // Add a submission but do not submit.
2909         $this->add_submission($student, $assign, 'Student submission text');
2911         $this->setUser($student);
2912         $output = $assign->view_student_summary($student, true);
2913         $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2915         // Check that a teacher can not edit the submission as they do not have the capability.
2916         $this->setUser($teacher);
2917         $this->expectException('moodle_exception');
2918         $this->expectExceptionMessage('error/nopermission');
2919         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2920     }
2922     /**
2923      * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
2924      */
2925     public function test_teacher_submit_for_student_with_capability() {
2926         global $PAGE;
2928         $this->resetAfterTest();
2929         $course = $this->getDataGenerator()->create_course();
2930         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2931         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2932         $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2934         $assign = $this->create_instance($course, [
2935             'assignsubmission_onlinetext_enabled' => 1,
2936             'submissiondrafts' => 1,
2937         ]);
2939         // Add the required capability.
2940         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2941         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2942         role_assign($roleid, $teacher->id, $assign->get_context()->id);
2943         accesslib_clear_all_caches_for_unit_testing();
2945         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2947         // Add a submission but do not submit.
2948         $this->add_submission($student, $assign, 'Student submission text');
2950         $this->setUser($student);
2951         $output = $assign->view_student_summary($student, true);
2952         $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2954         // Check that a teacher can edit the submission.
2955         $this->setUser($teacher);
2956         $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2958         $this->setUser($student);
2959         $output = $assign->view_student_summary($student, true);
2960         $this->assertStringNotContainsString('Student submission text', $output, 'Contains student submission text');
2961         $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains teacher edited submission text');
2963         // Check that the teacher can submit the students work.
2964         $this->setUser($teacher);
2965         $this->submit_for_grading($student, $assign, [], false);
2967         // Revert to draft so the student can edit it.
2968         $assign->revert_to_draft($student->id);
2970         $this->setUser($student);
2972         // Check that the submission text was saved.
2973         $output = $assign->view_student_summary($student, true);
2974         $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains student submission text');
2976         // Check that the student can submit their work.
2977         $this->submit_for_grading($student, $assign, []);
2979         $output = $assign->view_student_summary($student, true);
2980         $this->assertStringNotContainsString(get_string('addsubmission', 'assign'), $output);
2982         // An editing teacher without the extra role should still be able to revert to draft.
2983         $this->setUser($otherteacher);
2985         // Revert to draft so the submission is editable.
2986         $assign->revert_to_draft($student->id);
2987     }
2989     /**
2990      * Ensure that disabling submit after the cutoff date works as expected.
2991      */
2992     public function test_disable_submit_after_cutoff_date() {
2993         global $PAGE;
2995         $this->resetAfterTest();
2996         $course = $this->getDataGenerator()->create_course();
2997         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2999         $now = time();
3000         $tomorrow = $now + DAYSECS;
3001         $lastweek = $now - (7 * DAYSECS);
3002         $yesterday = $now - DAYSECS;
3004         $this->setAdminUser();
3005         $assign = $this->create_instance($course, [
3006             'duedate' => $yesterday,
3007             'cutoffdate' => $tomorrow,
3008             'assignsubmission_onlinetext_enabled' => 1,
3009         ]);
3011         $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
3013         // Student should be able to see an add submission button.
3014         $this->setUser($student);
3015         $output = $assign->view_student_summary($student, true);
3016         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
3018         // Add a submission but don't submit now.
3019         $this->add_submission($student, $assign);
3021         // Create another instance with cut-off and due-date already passed.
3022         $this->setAdminUser();
3023         $assign = $this->create_instance($course, [
3024             'duedate' => $lastweek,
3025             'cutoffdate' => $yesterday,
3026             'assignsubmission_onlinetext_enabled' => 1,
3027         ]);
3029         $this->setUser($student);
3030         $output = $assign->view_student_summary($student, true);
3031         $this->assertStringNotContainsString($output, get_string('editsubmission', 'assign'),
3032             'Should not be able to edit after cutoff date.');
3033         $this->assertStringNotContainsString($output, get_string('submitassignment', 'assign'),
3034             'Should not be able to submit after cutoff date.');
3035     }
3037     /**
3038      * Testing for submission comment plugin settings.
3039      *
3040      * @dataProvider submission_plugin_settings_provider
3041      * @param   bool    $globalenabled
3042      * @param   array   $instanceconfig
3043      * @param   bool    $isenabled
3044      */
3045     public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled) {
3046         global $CFG;
3048         $this->resetAfterTest();
3049         $course = $this->getDataGenerator()->create_course();
3051         $CFG->usecomments = $globalenabled;
3052         $assign = $this->create_instance($course, $instanceconfig);
3053         $plugin = $assign->get_submission_plugin_by_type('comments');
3054         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3055     }
3057     public function submission_plugin_settings_provider() {
3058         return [
3059             'CFG->usecomments true, empty config => Enabled by default' => [
3060                 true,
3061                 [],
3062                 true,
3063             ],
3064             'CFG->usecomments true, config enabled => Comments enabled' => [
3065                 true,
3066                 [
3067                     'assignsubmission_comments_enabled' => 1,
3068                 ],
3069                 true,
3070             ],
3071             'CFG->usecomments true, config idisabled => Comments enabled' => [
3072                 true,
3073                 [
3074                     'assignsubmission_comments_enabled' => 0,
3075                 ],
3076                 true,
3077             ],
3078             'CFG->usecomments false, empty config => Disabled by default' => [
3079                 false,
3080                 [],
3081                 false,
3082             ],
3083             'CFG->usecomments false, config enabled => Comments disabled' => [
3084                 false,
3085                 [
3086                     'assignsubmission_comments_enabled' => 1,
3087                 ],
3088                 false,
3089             ],
3090             'CFG->usecomments false, config disabled => Comments disabled' => [
3091                 false,
3092                 [
3093                     'assignsubmission_comments_enabled' => 0,
3094                 ],
3095                 false,
3096             ],
3097         ];
3098     }
3100     /**
3101      * Testing for comment inline settings
3102      */
3103     public function test_feedback_comment_commentinline() {
3104         global $CFG, $USER;
3106         $this->resetAfterTest();
3107         $course = $this->getDataGenerator()->create_course();
3108         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3109         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3111         $sourcetext = "Hello!
3113 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3115 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3116 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3118 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
3119 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
3120 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
3121 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
3122 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
3123 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3124 ";
3126         $this->setUser($teacher);
3127         $assign = $this->create_instance($course, [
3128             'assignsubmission_onlinetext_enabled' => 1,
3129             'assignfeedback_comments_enabled' => 1,
3130             'assignfeedback_comments_commentinline' => 1,
3131         ]);
3133         $this->setUser($student);
3135         // Add a submission but don't submit now.
3136         $this->add_submission($student, $assign, $sourcetext);
3138         $this->setUser($teacher);
3140         $data = new stdClass();
3141         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3142         $pagination = [
3143             'userid' => $student->id,
3144             'rownum' => 0,
3145             'last' => true,
3146             'useridlistid' => $assign->get_useridlist_key_id(),
3147             'attemptnumber' => 0,
3148         ];
3149         $formparams = array($assign, $data, $pagination);
3150         $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
3152         // We need to get the URL these will be transformed to.
3153         $context = context_user::instance($USER->id);
3154         $itemid = $data->assignfeedbackcomments_editor['itemid'];
3155         $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid;
3157         // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
3158         $filteredtext = "Hello!
3160 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3162 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3163 Plugin url outside a tag: $url/logo-240x60.gif
3165 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3166 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3167 Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" />
3168 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" />
3169 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a>
3170 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3171 ";
3173         $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3174     }
3176     /**
3177      * Testing for feedback comment plugin settings.
3178      *
3179      * @dataProvider feedback_plugin_settings_provider
3180      * @param   array   $instanceconfig
3181      * @param   bool    $isenabled
3182      */
3183     public function test_feedback_plugin_settings($instanceconfig, $isenabled) {
3184         $this->resetAfterTest();
3185         $course = $this->getDataGenerator()->create_course();
3187         $assign = $this->create_instance($course, $instanceconfig);
3188         $plugin = $assign->get_feedback_plugin_by_type('comments');
3189         $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3190     }
3192     public function feedback_plugin_settings_provider() {
3193         return [
3194             'No configuration => disabled' => [
3195                 [],
3196                 false,
3197             ],
3198             'Actively disabled' => [
3199                 [
3200                     'assignfeedback_comments_enabled' => 0,
3201                 ],
3202                 false,
3203             ],
3204             'Actively enabled' => [
3205                 [
3206                     'assignfeedback_comments_enabled' => 1,
3207                 ],
3208                 true,
3209             ],
3210         ];
3211     }
3213     /**
3214      * Testing if gradebook feedback plugin is enabled.
3215      */
3216     public function test_is_gradebook_feedback_enabled() {
3217         $this->resetAfterTest();
3218         $course = $this->getDataGenerator()->create_course();
3219         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3220         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3222         $adminconfig = get_config('assign');
3223         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3225         // Create assignment with gradebook feedback enabled and grade = 0.
3226         $assign = $this->create_instance($course, [
3227             "{$gradebookplugin}_enabled" => 1,
3228             'grades' => 0,
3229         ]);
3231         // Get gradebook feedback plugin.
3232         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3233         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3234         $this->assertEquals(1, $plugin->is_enabled('enabled'));
3235         $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3236     }
3238     /**
3239      * Testing if gradebook feedback plugin is disabled.
3240      */
3241     public function test_is_gradebook_feedback_disabled() {
3242         $this->resetAfterTest();
3243         $course = $this->getDataGenerator()->create_course();
3244         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3245         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3247         $adminconfig = get_config('assign');
3248         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3250         // Create assignment with gradebook feedback disabled and grade = 0.
3251         $assign = $this->create_instance($course, [
3252             "{$gradebookplugin}_enabled" => 0,
3253             'grades' => 0,
3254         ]);
3256         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3257         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3258         $this->assertEquals(0, $plugin->is_enabled('enabled'));
3259     }
3261     /**
3262      * Testing can_edit_submission.
3263      */
3264     public function test_can_edit_submission() {
3265         $this->resetAfterTest();
3266         $course = $this->getDataGenerator()->create_course();
3267         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3268         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3269         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3271         $assign = $this->create_instance($course, [
3272             'assignsubmission_onlinetext_enabled' => 1,
3273             'submissiondrafts' => 1,
3274         ]);
3276         // Check student can edit their own submission.
3277         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3279         // Check student cannot edit others submission.
3280         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3282         // Check teacher cannot (by default) edit a students submission.
3283         $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3284     }
3286     /**
3287      * Testing can_edit_submission with the editothersubmission capability.
3288      */
3289     public function test_can_edit_submission_with_editothersubmission() {
3290         $this->resetAfterTest();
3291         $course = $this->getDataGenerator()->create_course();
3292         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3293         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3294         $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3296         $assign = $this->create_instance($course, [
3297             'assignsubmission_onlinetext_enabled' => 1,
3298             'submissiondrafts' => 1,
3299         ]);
3301         // Add the required capability to edit a student submission.
3302         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3303         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3304         role_assign($roleid, $teacher->id, $assign->get_context()->id);
3305         accesslib_clear_all_caches_for_unit_testing();
3307         // Check student can edit their own submission.
3308         $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3310         // Check student cannot edit others submission.
3311         $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3313         // Retest - should now have access.
3314         $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3315     }
3317     /**
3318      * Testing can_edit_submission
3319      */
3320     public function test_can_edit_submission_separategroups() {
3321         $this->resetAfterTest();
3322         $course = $this->getDataGenerator()->create_course();
3323         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3325         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3326         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3327         $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3328         $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3330         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3331         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);