6ccc233ea1049bacf17296e29d07f319ab2a4af7
[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/base_test.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 mod_assign_base_testcase {
42     public function test_return_links() {
43         global $PAGE;
44         $this->setUser($this->editingteachers[0]);
45         $returnaction = 'RETURNACTION';
46         $returnparams = array('param'=>'1');
47         $assign = $this->create_instance();
48         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
49         $assign->register_return_link($returnaction, $returnparams);
50         $this->assertEquals($returnaction, $assign->get_return_action());
51         $this->assertEquals($returnparams, $assign->get_return_params());
52     }
54     public function test_get_feedback_plugins() {
55         $this->setUser($this->editingteachers[0]);
56         $assign = $this->create_instance();
57         $installedplugins = array_keys(core_component::get_plugin_list('assignfeedback'));
59         foreach ($assign->get_feedback_plugins() as $plugin) {
60             $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins');
61         }
62     }
64     public function test_get_submission_plugins() {
65         $this->setUser($this->editingteachers[0]);
66         $assign = $this->create_instance();
67         $installedplugins = array_keys(core_component::get_plugin_list('assignsubmission'));
69         foreach ($assign->get_submission_plugins() as $plugin) {
70             $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins');
71         }
72     }
74     public function test_is_blind_marking() {
75         $this->setUser($this->editingteachers[0]);
76         $assign = $this->create_instance(array('blindmarking'=>1));
77         $this->assertEquals(true, $assign->is_blind_marking());
79         // Test cannot see student names.
80         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
81         $output = $assign->get_renderer()->render($gradingtable);
82         $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
84         // Test students cannot reveal identities.
85         $nopermission = false;
86         $this->students[0]->ignoresesskey = true;
87         $this->setUser($this->students[0]);
88         $this->expectException('required_capability_exception');
89         $assign->reveal_identities();
90         $this->students[0]->ignoresesskey = false;
92         // Test teachers cannot reveal identities.
93         $nopermission = false;
94         $this->teachers[0]->ignoresesskey = true;
95         $this->setUser($this->teachers[0]);
96         $this->expectException('required_capability_exception');
97         $assign->reveal_identities();
98         $this->teachers[0]->ignoresesskey = false;
100         // Test sesskey is required.
101         $this->setUser($this->editingteachers[0]);
102         $this->expectException('moodle_exception');
103         $assign->reveal_identities();
105         // Test editingteacher can reveal identities if sesskey is ignored.
106         $this->editingteachers[0]->ignoresesskey = true;
107         $this->setUser($this->editingteachers[0]);
108         $assign->reveal_identities();
109         $this->assertEquals(false, $assign->is_blind_marking());
110         $this->editingteachers[0]->ignoresesskey = false;
112         // Test student names are visible.
113         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
114         $output = $assign->get_renderer()->render($gradingtable);
115         $this->assertEquals(false, strpos($output, get_string('hiddenuser', 'assign')));
117         // Set this back to default.
118         $this->editingteachers[0]->ignoresesskey = false;
119     }
121     /**
122      * Data provider for test_get_assign_perpage
123      *
124      * @return array Provider data
125      */
126     public function get_assign_perpage_provider() {
127         return array(
128             array(
129                 'maxperpage' => -1,
130                 'userprefs' => array(
131                     -1 => -1,
132                     10 => 10,
133                     20 => 20,
134                     50 => 50,
135                 ),
136             ),
137             array(
138                 'maxperpage' => 15,
139                 'userprefs' => array(
140                     -1 => 15,
141                     10 => 10,
142                     20 => 15,
143                     50 => 15,
144                 ),
145             ),
146         );
147     }
149     /**
150      * Test maxperpage
151      *
152      * @dataProvider get_assign_perpage_provider
153      * @param integer $maxperpage site config value
154      * @param array $userprefs Array of user preferences and expected page sizes
155      */
156     public function test_get_assign_perpage($maxperpage, $userprefs) {
158         $this->setUser($this->editingteachers[0]);
159         $assign = $this->create_instance();
160         set_config('maxperpage', $maxperpage, 'assign');
161         set_user_preference('assign_perpage', null);
162         $this->assertEquals(10, $assign->get_assign_perpage());
163         foreach ($userprefs as $pref => $perpage) {
164             set_user_preference('assign_perpage', $pref);
165             $this->assertEquals($perpage, $assign->get_assign_perpage());
166         }
167     }
169     /**
170      * Test submissions with extension date.
171      */
172     public function test_gradingtable_extension_due_date() {
173         global $PAGE;
175         // Setup the assignment.
176         $this->create_extra_users();
177         $this->setUser($this->editingteachers[0]);
178         $assign = $this->create_instance(array(
179             'assignsubmission_onlinetext_enabled'=>1,
180             'duedate' => time() - 4 * 24 * 60 * 60,
181          ));
182         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
183             'id' => $assign->get_course_module()->id,
184             'action' => 'grading',
185         )));
187         // Check that the assignment is late.
188         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
189         $output = $assign->get_renderer()->render($gradingtable);
190         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
191         $this->assertContains(get_string('overdue', 'assign', format_time(4*24*60*60)), $output);
193         // Grant an extension.
194         $extendedtime = time() + 2 * 24 * 60 * 60;
195         $assign->testable_save_user_extension($this->students[0]->id, $extendedtime);
196         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
197         $output = $assign->get_renderer()->render($gradingtable);
198         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
199         $this->assertContains(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
201         // Simulate a submission.
202         $this->setUser($this->students[0]);
203         $submission = $assign->get_user_submission($this->students[0]->id, true);
204         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
205         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
206         $data = new stdClass();
207         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
208                                          'text'=>'Submission text',
209                                          'format'=>FORMAT_MOODLE);
210         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
211         $plugin->save($submission, $data);
213         // Verify output.
214         $this->setUser($this->editingteachers[0]);
215         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
216         $output = $assign->get_renderer()->render($gradingtable);
217         $this->assertContains(get_string('submissionstatus_submitted', 'assign'), $output);
218         $this->assertContains(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
219     }
221     /**
222      * Test that late submissions with extension date calculate correctly.
223      */
224     public function test_gradingtable_extension_date_calculation_for_lateness() {
225         global $PAGE;
227         // Setup the assignment.
228         $this->create_extra_users();
229         $this->setUser($this->editingteachers[0]);
230         $time = time();
231         $assign = $this->create_instance(array(
232             'assignsubmission_onlinetext_enabled'=>1,
233             'duedate' => $time - 4 * 24 * 60 * 60,
234          ));
235         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
236             'id' => $assign->get_course_module()->id,
237             'action' => 'grading',
238         )));
240         // Check that the assignment is late.
241         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
242         $output = $assign->get_renderer()->render($gradingtable);
243         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
244         $difftime = time() - $time;
245         $this->assertContains(get_string('overdue', 'assign', format_time(4*24*60*60 + $difftime)), $output);
247         // Grant an extension that is in the past.
248         $assign->testable_save_user_extension($this->students[0]->id, $time - 2 * 24 * 60 * 60);
249         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
250         $output = $assign->get_renderer()->render($gradingtable);
251         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
252         $this->assertContains(get_string('userextensiondate', 'assign', userdate($time - 2*24*60*60)), $output);
253         $difftime = time() - $time;
254         $this->assertContains(get_string('overdue', 'assign', format_time(2*24*60*60 + $difftime)), $output);
256         // Simulate a submission.
257         $this->setUser($this->students[0]);
258         $submission = $assign->get_user_submission($this->students[0]->id, true);
259         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
260         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
261         $data = new stdClass();
262         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
263                                          'text'=>'Submission text',
264                                          'format'=>FORMAT_MOODLE);
265         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
266         $plugin->save($submission, $data);
267         $submittedtime = time();
269         // Verify output.
270         $this->setUser($this->editingteachers[0]);
271         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
272         $output = $assign->get_renderer()->render($gradingtable);
273         $this->assertContains(get_string('submissionstatus_submitted', 'assign'), $output);
274         $this->assertContains(get_string('userextensiondate', 'assign', userdate($time - 2*24*60*60)), $output);
276         $difftime = $submittedtime - $time;
277         $this->assertContains(get_string('submittedlateshort', 'assign', format_time(2*24*60*60 + $difftime)), $output);
278     }
280     public function test_gradingtable_status_rendering() {
281         global $PAGE;
283         // Setup the assignment.
284         $this->create_extra_users();
285         $this->setUser($this->editingteachers[0]);
286         $time = time();
287         $assign = $this->create_instance(array(
288             'assignsubmission_onlinetext_enabled' => 1,
289             'duedate' => $time - 4 * 24 * 60 * 60,
290          ));
291         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
292             'id' => $assign->get_course_module()->id,
293             'action' => 'grading',
294         )));
296         // Check that the assignment is late.
297         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
298         $output = $assign->get_renderer()->render($gradingtable);
299         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
300         $difftime = time() - $time;
301         $this->assertContains(get_string('overdue', 'assign', format_time(4 * 24 * 60 * 60 + $difftime)), $output);
303         // Simulate a student viewing the assignment without submitting.
304         $this->setUser($this->students[0]);
305         $submission = $assign->get_user_submission($this->students[0]->id, true);
306         $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
307         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
308         $submittedtime = time();
310         // Verify output.
311         $this->setUser($this->editingteachers[0]);
312         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
313         $output = $assign->get_renderer()->render($gradingtable);
314         $difftime = $submittedtime - $time;
315         $this->assertContains(get_string('overdue', 'assign', format_time(4 * 24 * 60 * 60 + $difftime)), $output);
317         $document = new DOMDocument();
318         @$document->loadHTML($output);
319         $xpath = new DOMXPath($document);
320         $this->assertEquals('', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])'));
321     }
323     /**
324      * Check that group submission information is rendered correctly in the
325      * grading table.
326      */
327     public function test_gradingtable_group_submissions_rendering() {
328         global $PAGE;
330         $this->create_extra_users();
331         // Now verify group assignments.
332         $this->setUser($this->teachers[0]);
333         $assign = $this->create_instance(array(
334             'teamsubmission' => 1,
335             'assignsubmission_onlinetext_enabled' => 1,
336             'submissiondrafts' => 1,
337             'requireallteammemberssubmit' => 0,
338         ));
339         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
340             'id' => $assign->get_course_module()->id,
341             'action' => 'grading',
342         )));
344         // Add a submission.
345         $this->setUser($this->extrastudents[0]);
346         $data = new stdClass();
347         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
348                                          'text'=>'Submission text',
349                                          'format'=>FORMAT_MOODLE);
350         $notices = array();
351         $assign->save_submission($data, $notices);
353         $submission = $assign->get_group_submission($this->extrastudents[0]->id, 0, true);
354         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
355         $assign->testable_update_submission($submission, $this->extrastudents[0]->id, true, true);
357         // Check output.
358         $this->setUser($this->teachers[0]);
359         $gradingtable = new assign_grading_table($assign, 4, '', 0, true);
360         $output = $assign->get_renderer()->render($gradingtable);
361         $document = new DOMDocument();
362         @$document->loadHTML($output);
363         $xpath = new DOMXPath($document);
365         // Check status.
366         $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c4"]/div[@class="submissionstatussubmitted"])'));
367         $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c4"]/div[@class="submissionstatussubmitted"])'));
369         // Check submission last modified date
370         $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])')));
371         $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c8"])')));
373         // Check group.
374         $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c5"])'));
375         $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c5"])'));
377         // Check submission text.
378         $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c9"]/div/div)'));
379         $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c9"]/div/div)'));
381         // Check comments can be made.
382         $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r0_c10"]//textarea)'));
383         $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r3_c10"]//textarea)'));
384     }
386     public function test_show_intro() {
387         // Test whether we are showing the intro at the correct times.
388         $this->setUser($this->editingteachers[0]);
389         $assign = $this->create_instance(array('alwaysshowdescription'=>1));
391         $this->assertEquals(true, $assign->testable_show_intro());
393         $tomorrow = time() + (24*60*60);
395         $assign = $this->create_instance(array('alwaysshowdescription'=>0,
396                                                'allowsubmissionsfromdate'=>$tomorrow));
397         $this->assertEquals(false, $assign->testable_show_intro());
398         $yesterday = time() - (24*60*60);
399         $assign = $this->create_instance(array('alwaysshowdescription'=>0,
400                                                'allowsubmissionsfromdate'=>$yesterday));
401         $this->assertEquals(true, $assign->testable_show_intro());
402     }
404     public function test_has_submissions_or_grades() {
405         $this->setUser($this->editingteachers[0]);
406         $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled'=>1));
408         $instance = $assign->get_instance();
410         // Should start empty.
411         $this->assertEquals(false, $assign->has_submissions_or_grades());
413         // Simulate a submission.
414         $this->setUser($this->students[0]);
415         $submission = $assign->get_user_submission($this->students[0]->id, true);
417         // The submission is still new.
418         $this->assertEquals(false, $assign->has_submissions_or_grades());
420         // Submit the submission.
421         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
422         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
423         $data = new stdClass();
424         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
425                                          'text'=>'Submission text',
426                                          'format'=>FORMAT_MOODLE);
427         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
428         $plugin->save($submission, $data);
430         // Now test again.
431         $this->assertEquals(true, $assign->has_submissions_or_grades());
432         // Set this back to default.
433         $this->students[0]->ignoresesskey = false;
434     }
436     public function test_delete_grades() {
437         $this->setUser($this->editingteachers[0]);
438         $assign = $this->create_instance();
440         // Simulate adding a grade.
441         $this->setUser($this->teachers[0]);
442         $data = new stdClass();
443         $data->grade = '50.0';
444         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
446         // Now see if the data is in the gradebook.
447         $gradinginfo = grade_get_grades($this->course->id,
448                                         'mod',
449                                         'assign',
450                                         $assign->get_instance()->id);
452         $this->assertNotEquals(0, count($gradinginfo->items));
454         $assign->testable_delete_grades();
455         $gradinginfo = grade_get_grades($this->course->id,
456                                         'mod',
457                                         'assign',
458                                         $assign->get_instance()->id);
460         $this->assertEquals(0, count($gradinginfo->items));
461     }
463     public function test_delete_instance() {
464         $this->setUser($this->editingteachers[0]);
465         $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled'=>1));
467         // Simulate adding a grade.
468         $this->setUser($this->teachers[0]);
469         $data = new stdClass();
470         $data->grade = '50.0';
471         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
473         // Simulate a submission.
474         $this->setUser($this->students[0]);
475         $submission = $assign->get_user_submission($this->students[0]->id, true);
476         $data = new stdClass();
477         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
478                                          'text'=>'Submission text',
479                                          'format'=>FORMAT_MOODLE);
480         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
481         $plugin->save($submission, $data);
483         // Now try and delete.
484         $this->assertEquals(true, $assign->delete_instance());
485     }
487     public function test_reset_userdata() {
488         global $DB;
490         $now = time();
491         $this->setUser($this->editingteachers[0]);
492         $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled'=>1,
493                                                'duedate'=>$now));
495         // Simulate adding a grade.
496         $this->setUser($this->teachers[0]);
497         $data = new stdClass();
498         $data->grade = '50.0';
499         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
501         // Simulate a submission.
502         $this->setUser($this->students[0]);
503         $submission = $assign->get_user_submission($this->students[0]->id, true);
504         $data = new stdClass();
505         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
506                                          'text'=>'Submission text',
507                                          'format'=>FORMAT_MOODLE);
508         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
509         $plugin->save($submission, $data);
511         $this->assertEquals(true, $assign->has_submissions_or_grades());
512         // Now try and reset.
513         $data = new stdClass();
514         $data->reset_assign_submissions = 1;
515         $data->reset_gradebook_grades = 1;
516         $data->reset_assign_user_overrides = 1;
517         $data->reset_assign_group_overrides = 1;
518         $data->courseid = $this->course->id;
519         $data->timeshift = 24*60*60;
520         $this->setUser($this->editingteachers[0]);
521         $assign->reset_userdata($data);
522         $this->assertEquals(false, $assign->has_submissions_or_grades());
524         // Reload the instance data.
525         $instance = $DB->get_record('assign', array('id'=>$assign->get_instance()->id));
526         $this->assertEquals($now + 24*60*60, $instance->duedate);
528         // Test reset using assign_reset_userdata().
529         $assignduedate = $instance->duedate; // Keep old updated value for comparison.
530         $data->timeshift = 2*24*60*60;
531         assign_reset_userdata($data);
532         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
533         $this->assertEquals($assignduedate + 2*24*60*60, $instance->duedate);
535         // Create one more assignment and reset, make sure time shifted for previous assignment is not changed.
536         $assign2 = $this->create_instance(array('assignsubmission_onlinetext_enabled' => 1,
537                                                'duedate' => $now));
538         $assignduedate = $instance->duedate;
539         $data->timeshift = 3*24*60*60;
540         $assign2->reset_userdata($data);
541         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
542         $this->assertEquals($assignduedate, $instance->duedate);
543         $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
544         $this->assertEquals($now + 3*24*60*60, $instance2->duedate);
546         // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date.
547         $assignduedate = $instance->duedate;
548         $assign2duedate = $instance2->duedate;
549         $data->timeshift = 4*24*60*60;
550         assign_reset_userdata($data);
551         $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
552         $this->assertEquals($assignduedate + 4*24*60*60, $instance->duedate);
553         $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
554         $this->assertEquals($assign2duedate + 4*24*60*60, $instance2->duedate);
555     }
557     public function test_plugin_settings() {
558         global $DB;
560         $now = time();
561         $this->setUser($this->editingteachers[0]);
562         $assign = $this->create_instance(array('assignsubmission_file_enabled'=>1,
563                                                'assignsubmission_file_maxfiles'=>12,
564                                                'assignsubmission_file_maxsizebytes'=>10));
566         $plugin = $assign->get_submission_plugin_by_type('file');
567         $this->assertEquals('12', $plugin->get_config('maxfilesubmissions'));
568     }
570     public function test_update_calendar() {
571         global $DB;
573         $this->setUser($this->editingteachers[0]);
574         $userctx = context_user::instance($this->editingteachers[0]->id)->id;
576         // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey.
577         $draftid = file_get_unused_draft_itemid();
578         $_REQUEST['introeditor'] = $draftid;
579         $_POST['introeditor'] = $draftid;
580         $_POST['sesskey'] = sesskey();
582         // Write links to a draft area.
583         $fakearealink1 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">link</a>', 'draftfile.php', $userctx,
584             'user', 'draft', $draftid);
585         $fakearealink2 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">new</a>', 'draftfile.php', $userctx,
586             'user', 'draft', $draftid);
588         // Create a new assignment with links to a draft area.
589         $now = time();
590         $assign = $this->create_instance(array(
591             'duedate' => $now,
592             'intro' => $fakearealink1,
593             'introformat' => FORMAT_HTML
594         ));
596         // See if there is an event in the calendar.
597         $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id);
598         $event = $DB->get_record('event', $params);
599         $this->assertNotEmpty($event);
600         $this->assertSame('link', $event->description);     // The pluginfile links are removed.
602         // Make sure the same works when updating the assignment.
603         $instance = $assign->get_instance();
604         $instance->instance = $instance->id;
605         $instance->intro = $fakearealink2;
606         $instance->introformat = FORMAT_HTML;
607         $assign->update_instance($instance);
608         $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
609         $event = $DB->get_record('event', $params);
610         $this->assertNotEmpty($event);
611         $this->assertSame('new', $event->description);     // The pluginfile links are removed.
613         // Create an assignment with a description that should be hidden.
614         $assign = $this->create_instance(array('duedate'=>$now + 160,
615                                                'alwaysshowdescription'=>false,
616                                                'allowsubmissionsfromdate'=>$now + 60,
617                                                'intro'=>'Some text'));
619         // Get the event from the calendar.
620         $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id);
621         $event = $DB->get_record('event', $params);
623         $this->assertEmpty($event->description);
625         // Change the allowsubmissionfromdate to the past - do this directly in the DB
626         // because if we call the assignment update method - it will update the calendar
627         // and we want to test that this works from cron.
628         $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id'=>$assign->get_instance()->id));
629         // Run cron to update the event in the calendar.
630         assign::cron();
631         $event = $DB->get_record('event', $params);
633         $this->assertContains('Some text', $event->description);
635     }
637     public function test_update_instance() {
638         global $DB;
640         $this->setUser($this->editingteachers[0]);
641         $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled'=>1));
643         $now = time();
644         $instance = $assign->get_instance();
645         $instance->duedate = $now;
646         $instance->instance = $instance->id;
647         $instance->assignsubmission_onlinetext_enabled = 1;
649         $assign->update_instance($instance);
651         $instance = $DB->get_record('assign', array('id'=>$assign->get_instance()->id));
652         $this->assertEquals($now, $instance->duedate);
653     }
655     public function test_cannot_submit_empty() {
656         global $PAGE;
658         $this->setUser($this->editingteachers[0]);
659         $assign = $this->create_instance(array('submissiondrafts'=>1));
661         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
663         // Test you cannot see the submit button for an offline assignment regardless.
664         $this->setUser($this->students[0]);
665         $output = $assign->view_student_summary($this->students[0], true);
666         $this->assertNotContains(get_string('submitassignment', 'assign'), $output, 'Can submit empty offline assignment');
668         // Test you cannot see the submit button for an online text assignment with no submission.
669         $this->setUser($this->editingteachers[0]);
670         $instance = $assign->get_instance();
671         $instance->instance = $instance->id;
672         $instance->assignsubmission_onlinetext_enabled = 1;
674         $assign->update_instance($instance);
675         $this->setUser($this->students[0]);
676         $output = $assign->view_student_summary($this->students[0], true);
677         $this->assertNotContains(get_string('submitassignment', 'assign'), $output, 'Cannot submit empty onlinetext assignment');
679         // Simulate a submission.
680         $submission = $assign->get_user_submission($this->students[0]->id, true);
681         $data = new stdClass();
682         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
683                                          'text'=>'Submission text',
684                                          'format'=>FORMAT_MOODLE);
685         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
686         $plugin->save($submission, $data);
687         // Test you can see the submit button for an online text assignment with a submission.
688         $output = $assign->view_student_summary($this->students[0], true);
689         $this->assertContains(get_string('submitassignment', 'assign'), $output, 'Can submit non empty onlinetext assignment');
690     }
692     /**
693      * Test new_submission_empty
694      *
695      * We only test combinations of plugins here. Individual plugins are tested
696      * in their respective test files.
697      *
698      * @dataProvider test_new_submission_empty_testcases
699      * @param string $data The file submission data
700      * @param bool $expected The expected return value
701      */
702     public function test_new_submission_empty($data, $expected) {
703         $this->resetAfterTest();
704         $assign = $this->create_instance(['assignsubmission_file_enabled' => 1,
705                                           'assignsubmission_file_maxfiles' => 12,
706                                           'assignsubmission_file_maxsizebytes' => 10,
707                                           'assignsubmission_onlinetext_enabled' => 1]);
708         $this->setUser($this->students[0]);
709         $submission = new stdClass();
711         if ($data['file'] && isset($data['file']['filename'])) {
712             $itemid = file_get_unused_draft_itemid();
713             $submission->files_filemanager = $itemid;
714             $data['file'] += ['contextid' => context_user::instance($this->students[0]->id)->id, 'itemid' => $itemid];
715             $fs = get_file_storage();
716             $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']);
717         }
719         if ($data['onlinetext']) {
720             $submission->onlinetext_editor = ['text' => $data['onlinetext']];
721         }
723         $result = $assign->new_submission_empty($submission);
724         $this->assertTrue($result === $expected);
725     }
727     /**
728      * Dataprovider for the test_new_submission_empty testcase
729      *
730      * @return array of testcases
731      */
732     public function test_new_submission_empty_testcases() {
733         return [
734             'With file and onlinetext' => [
735                 [
736                     'file' => [
737                         'component' => 'user',
738                         'filearea' => 'draft',
739                         'filepath' => '/',
740                         'filename' => 'not_a_virus.exe'
741                     ],
742                     'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu'
743                 ],
744                 false
745             ]
746         ];
747     }
749     public function test_list_participants() {
750         global $CFG, $DB;
752         $this->create_extra_users();
753         $this->setUser($this->editingteachers[0]);
754         $assign = $this->create_instance(array('grade'=>100));
756         $this->assertEquals(self::DEFAULT_STUDENT_COUNT + self::EXTRA_STUDENT_COUNT, count($assign->list_participants(null, true)));
758         // Teacher with user preference set should see suspended users as well.
759         set_user_preference('grade_report_showonlyactiveenrol', false);
760         $assign = $this->create_instance(array('grade'=>100));
761         $this->assertEquals(self::DEFAULT_STUDENT_COUNT + self::EXTRA_STUDENT_COUNT + self::EXTRA_SUSPENDED_COUNT,
762                 count($assign->list_participants(null, true)));
764         // Non-editing teacher should not see suspended users, even if user preference is set.
765         $this->setUser($this->teachers[0]);
766         set_user_preference('grade_report_showonlyactiveenrol', false);
767         $assign = $this->create_instance(array('grade'=>100));
768         $this->assertEquals(self::DEFAULT_STUDENT_COUNT + self::EXTRA_STUDENT_COUNT, count($assign->list_participants(null, true)));
770         // Turn on availability and a group restriction, and check that it doesn't
771         // show users who aren't in the group.
772         $CFG->enableavailability = true;
773         $specialgroup = $this->getDataGenerator()->create_group(
774                 array('courseid' => $this->course->id));
775         $assign = $this->create_instance(array('grade' => 100,
776                 'availability' => json_encode(\core_availability\tree::get_root_json(
777                     array(\availability_group\condition::get_json($specialgroup->id))))));
778         groups_add_member($specialgroup, $this->students[0]);
779         groups_add_member($specialgroup, $this->students[1]);
780         $this->assertEquals(2, count($assign->list_participants(null, true)));
781     }
783     public function test_get_participant_user_not_exist() {
784         $assign = $this->create_instance(array('grade' => 100));
785         $this->assertNull($assign->get_participant('-1'));
786     }
788     public function test_get_participant_not_enrolled() {
789         $assign = $this->create_instance(array('grade' => 100));
790         $user = $this->getDataGenerator()->create_user();
791         $this->assertNull($assign->get_participant($user->id));
792     }
794     public function test_get_participant_no_submission() {
795         $assign = $this->create_instance(array('grade' => 100));
796         $student = $this->students[0];
797         $participant = $assign->get_participant($student->id);
799         $this->assertEquals($student->id, $participant->id);
800         $this->assertFalse($participant->submitted);
801         $this->assertFalse($participant->requiregrading);
802         $this->assertFalse($participant->grantedextension);
803     }
805     public function test_get_participant_granted_extension() {
806         $assign = $this->create_instance(array('grade' => 100));
807         $student = $this->students[0];
808         $this->setUser($this->editingteachers[0]);
809         $assign->save_user_extension($student->id, time());
810         $participant = $assign->get_participant($student->id);
812         $this->assertEquals($student->id, $participant->id);
813         $this->assertFalse($participant->submitted);
814         $this->assertFalse($participant->requiregrading);
815         $this->assertTrue($participant->grantedextension);
816     }
818     public function test_get_participant_with_ungraded_submission() {
819         $assign = $this->create_instance(array('grade' => 100));
820         $student = $this->students[0];
821         $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
823         $this->setUser($student);
825         // Simulate a submission.
826         $data = new stdClass();
827         $data->onlinetext_editor = array(
828             'itemid' => file_get_unused_draft_itemid(),
829             'text' => 'Student submission text',
830             'format' => FORMAT_MOODLE
831         );
833         $notices = array();
834         $assign->save_submission($data, $notices);
836         $data = new stdClass;
837         $data->userid = $student->id;
838         $assign->submit_for_grading($data, array());
840         $participant = $assign->get_participant($student->id);
842         $this->assertEquals($student->id, $participant->id);
843         $this->assertTrue($participant->submitted);
844         $this->assertTrue($participant->requiregrading);
845         $this->assertFalse($participant->grantedextension);
846     }
848     public function test_get_participant_with_graded_submission() {
849         $assign = $this->create_instance(array('grade' => 100));
850         $student = $this->students[0];
851         $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
853         $this->setUser($student);
855         // Simulate a submission.
856         $data = new stdClass();
857         $data->onlinetext_editor = array(
858             'itemid' => file_get_unused_draft_itemid(),
859             'text' => 'Student submission text',
860             'format' => FORMAT_MOODLE
861         );
863         $notices = array();
864         $assign->save_submission($data, $notices);
866         $data = new stdClass;
867         $data->userid = $student->id;
868         $assign->submit_for_grading($data, array());
870         // This is to make sure the grade happens after the submission because
871         // we have no control over the timemodified values.
872         $this->waitForSecond();
873         // Grade the submission.
874         $this->setUser($this->teachers[0]);
876         $data = new stdClass();
877         $data->grade = '50.0';
878         $assign->testable_apply_grade_to_user($data, $student->id, 0);
880         $participant = $assign->get_participant($student->id);
882         $this->assertEquals($student->id, $participant->id);
883         $this->assertTrue($participant->submitted);
884         $this->assertFalse($participant->requiregrading);
885         $this->assertFalse($participant->grantedextension);
886     }
888     public function test_count_teams() {
889         $this->create_extra_users();
890         $this->setUser($this->editingteachers[0]);
891         $assign1 = $this->create_instance(array('teamsubmission' => 1));
892         $this->assertEquals(self::GROUP_COUNT + 1, $assign1->count_teams());
894         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $this->course->id));
895         $this->getDataGenerator()->create_grouping_group(array('groupid' => $this->groups[0]->id, 'groupingid' => $grouping->id));
896         $this->getDataGenerator()->create_grouping_group(array('groupid' => $this->groups[1]->id, 'groupingid' => $grouping->id));
898         // No active group and non group submissions allowed => 2 groups + the default one.
899         $params = array(
900             'teamsubmission' => 1,
901             'teamsubmissiongroupingid' => $grouping->id,
902             'preventsubmissionnotingroup' => false
903         );
904         $assign2 = $this->create_instance($params);
905         $this->assertEquals(3, $assign2->count_teams());
907         // An active group => Just the selected one.
908         $this->assertEquals(1, $assign2->count_teams($this->groups[0]->id));
910         // No active group and non group submissions allowed => 2 groups + no default one.
911         $params = array('teamsubmission' => 1, 'teamsubmissiongroupingid' => $grouping->id, 'preventsubmissionnotingroup' => true);
912         $assign3 = $this->create_instance($params);
913         $this->assertEquals(2, $assign3->count_teams());
915         $assign4 = $this->create_instance(array('teamsubmission' => 1, 'preventsubmissionnotingroup' => true));
916         $this->assertEquals(self::GROUP_COUNT, $assign4->count_teams());
917     }
919     public function test_submit_to_default_group() {
920         global $DB, $SESSION;
922         $this->preventResetByRollback();
923         $sink = $this->redirectMessages();
925         $this->setUser($this->editingteachers[0]);
926         $params = array('teamsubmission' => 1,
927                         'assignsubmission_onlinetext_enabled' => 1,
928                         'submissiondrafts' => 0,
929                         'groupmode' => VISIBLEGROUPS);
930         $assign = $this->create_instance($params);
932         $newstudent = $this->getDataGenerator()->create_user();
933         $studentrole = $DB->get_record('role', array('shortname'=>'student'));
934         $this->getDataGenerator()->enrol_user($newstudent->id,
935                                               $this->course->id,
936                                               $studentrole->id);
937         $this->setUser($newstudent);
938         $data = new stdClass();
939         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
940                                          'text'=>'Submission text',
941                                          'format'=>FORMAT_MOODLE);
942         $notices = array();
944         $group = $assign->get_submission_group($newstudent->id);
945         $this->assertFalse($group, 'New student is in default group');
946         $assign->save_submission($data, $notices);
947         $this->assertEmpty($notices, 'No errors on save submission');
949         // Set active groups to all groups.
950         $this->setUser($this->editingteachers[0]);
951         $SESSION->activegroup[$this->course->id]['aag'][0] = 0;
952         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
954         // Set an active group.
955         $anothergroup = $this->groups[0];
956         $SESSION->activegroup[$this->course->id]['aag'][0] = (int)$anothergroup->id;
957         $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
959         $sink->close();
960     }
962     public function test_count_submissions() {
963         global $SESSION;
965         $this->create_extra_users();
966         $this->setUser($this->editingteachers[0]);
967         $assign1 = $this->create_instance(array('assignsubmission_onlinetext_enabled' => 1));
969         // Simulate a submission.
970         $this->setUser($this->extrastudents[0]);
971         $submission = $assign1->get_user_submission($this->extrastudents[0]->id, true);
972         $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
973         $assign1->testable_update_submission($submission, $this->extrastudents[0]->id, true, false);
974         // Leave this one as DRAFT.
975         $data = new stdClass();
976         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
977                                          'text'=>'Submission text',
978                                          'format'=>FORMAT_MOODLE);
979         $plugin = $assign1->get_submission_plugin_by_type('onlinetext');
980         $plugin->save($submission, $data);
982         // Simulate adding a grade.
983         $this->setUser($this->teachers[0]);
984         $data = new stdClass();
985         $data->grade = '50.0';
986         $assign1->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
988         // Simulate a submission.
989         $this->setUser($this->extrastudents[1]);
990         $submission = $assign1->get_user_submission($this->extrastudents[1]->id, true);
991         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
992         $assign1->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
993         $data = new stdClass();
994         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
995                                          'text'=>'Submission text',
996                                          'format'=>FORMAT_MOODLE);
997         $plugin = $assign1->get_submission_plugin_by_type('onlinetext');
998         $plugin->save($submission, $data);
1000         // Simulate a submission.
1001         $this->setUser($this->extrastudents[2]);
1002         $submission = $assign1->get_user_submission($this->extrastudents[2]->id, true);
1003         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1004         $assign1->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1005         $data = new stdClass();
1006         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
1007                                          'text'=>'Submission text',
1008                                          'format'=>FORMAT_MOODLE);
1009         $plugin = $assign1->get_submission_plugin_by_type('onlinetext');
1010         $plugin->save($submission, $data);
1012         // Simulate a submission.
1013         $this->setUser($this->extrastudents[3]);
1014         $submission = $assign1->get_user_submission($this->extrastudents[3]->id, true);
1015         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1016         $assign1->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1017         $data = new stdClass();
1018         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
1019                                          'text'=>'Submission text',
1020                                          'format'=>FORMAT_MOODLE);
1021         $plugin = $assign1->get_submission_plugin_by_type('onlinetext');
1022         $plugin->save($submission, $data);
1024         // Simulate a submission for suspended user, this will never be counted.
1025         $this->setUser($this->extrastudents[3]);
1026         $submission = $assign1->get_user_submission($this->extrasuspendedstudents[0]->id, true);
1027         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1028         $assign1->testable_update_submission($submission, $this->extrasuspendedstudents[0]->id, true, false);
1029         $data = new stdClass();
1030         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
1031                                          'text'=>'Submission text',
1032                                          'format'=>FORMAT_MOODLE);
1033         $plugin = $assign1->get_submission_plugin_by_type('onlinetext');
1034         $plugin->save($submission, $data);
1036         // Wait 1 second so the submission and grade do not have the same timemodified.
1037         $this->waitForSecond();
1038         // Simulate adding a grade.
1039         $this->setUser($this->editingteachers[0]);
1040         $data = new stdClass();
1041         $data->grade = '50.0';
1042         $assign1->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1043         $assign1->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1045         // Create a new submission with status NEW.
1046         $this->setUser($this->extrastudents[4]);
1047         $submission = $assign1->get_user_submission($this->extrastudents[4]->id, true);
1049         $this->assertEquals(2, $assign1->count_grades());
1050         $this->assertEquals(4, $assign1->count_submissions());
1051         $this->assertEquals(5, $assign1->count_submissions(true));
1052         $this->assertEquals(2, $assign1->count_submissions_need_grading());
1053         $this->assertEquals(3, $assign1->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1054         $this->assertEquals(1, $assign1->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1056         // Groups.
1057         $assign2 = $this->create_instance(array(
1058             'assignsubmission_onlinetext_enabled' => 1,
1059             'groupmode' => VISIBLEGROUPS
1060         ));
1062         $this->setUser($this->extrastudents[1]);
1063         $submission = $assign2->get_user_submission($this->extrastudents[1]->id, true);
1064         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1065         $assign2->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1066         $data = new stdClass();
1067         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1068                                          'text' => 'Submission text',
1069                                          'format' => FORMAT_MOODLE);
1070         $plugin = $assign2->get_submission_plugin_by_type('onlinetext');
1071         $plugin->save($submission, $data);
1073         $this->assertEquals(1, $assign2->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1075         // Set active groups to all groups.
1076         $this->setUser($this->editingteachers[0]);
1077         $SESSION->activegroup[$this->course->id]['aag'][0] = 0;
1078         $this->assertEquals(1, $assign2->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1080         // Set the user group.
1081         $studentgroups = groups_get_user_groups($this->course->id, $this->extrastudents[1]->id);
1082         $this->assertEquals(1, count($studentgroups));
1083         $studentgroup = array_pop($studentgroups);
1084         $SESSION->activegroup[$this->course->id]['aag'][0] = $studentgroup[0];
1085         $this->assertEquals(1, $assign2->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1087         // Set another group.
1088         $anothergroup = $this->groups[0];
1089         $this->assertNotEquals($anothergroup->id, $studentgroup[0]);
1090         $SESSION->activegroup[$this->course->id]['aag'][0] = (int)$anothergroup->id;
1091         $this->assertEquals(0, $assign2->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1092     }
1094     public function test_count_submissions_for_groups() {
1095         $this->create_extra_users();
1096         $groupid = null;
1097         $this->setUser($this->editingteachers[0]);
1098         $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled' => 1, 'teamsubmission' => 1));
1100         // Simulate a submission.
1101         $this->setUser($this->extrastudents[0]);
1102         $submission = $assign->get_group_submission($this->extrastudents[0]->id, $groupid, true);
1103         $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1104         $assign->testable_update_submission($submission, $this->extrastudents[0]->id, true, false);
1105         // Leave this one as DRAFT.
1106         $data = new stdClass();
1107         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1108                                          'text' => 'Submission text',
1109                                          'format' => FORMAT_MOODLE);
1110         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1111         $plugin->save($submission, $data);
1113         // Simulate adding a grade.
1114         $this->setUser($this->teachers[0]);
1115         $data = new stdClass();
1116         $data->grade = '50.0';
1117         $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
1119         // Simulate a submission.
1120         $this->setUser($this->extrastudents[1]);
1121         $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
1122         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1123         $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1124         $data = new stdClass();
1125         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1126                                          'text' => 'Submission text',
1127                                          'format' => FORMAT_MOODLE);
1128         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1129         $plugin->save($submission, $data);
1131         // Simulate a submission.
1132         $this->setUser($this->extrastudents[2]);
1133         $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
1134         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1135         $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1136         $data = new stdClass();
1137         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1138                                          'text' => 'Submission text',
1139                                          'format' => FORMAT_MOODLE);
1140         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1141         $plugin->save($submission, $data);
1143         // Simulate a submission.
1144         $this->setUser($this->extrastudents[3]);
1145         $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
1146         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1147         $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1148         $data = new stdClass();
1149         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1150                                          'text' => 'Submission text',
1151                                          'format' => FORMAT_MOODLE);
1152         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1153         $plugin->save($submission, $data);
1155         // Simulate adding a grade.
1156         $this->setUser($this->editingteachers[0]);
1157         $data = new stdClass();
1158         $data->grade = '50.0';
1159         $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1160         $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1162         // Create a new submission with status NEW.
1163         $this->setUser($this->extrastudents[4]);
1164         $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
1166         $this->assertEquals(2, $assign->count_grades());
1167         $this->assertEquals(4, $assign->count_submissions());
1168         $this->assertEquals(5, $assign->count_submissions(true));
1169         $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1170         $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1171     }
1173     public function test_get_grading_userid_list() {
1174         $this->create_extra_users();
1175         $this->setUser($this->editingteachers[0]);
1176         $assign = $this->create_instance();
1178         $users = $assign->testable_get_grading_userid_list();
1179         $this->assertEquals(self::DEFAULT_STUDENT_COUNT + self::EXTRA_STUDENT_COUNT, count($users));
1181         $this->setUser($this->editingteachers[0]);
1182         set_user_preference('grade_report_showonlyactiveenrol', false);
1183         $assign = $this->create_instance();
1185         $users = $assign->testable_get_grading_userid_list();
1186         $this->assertEquals(self::DEFAULT_STUDENT_COUNT + self::EXTRA_STUDENT_COUNT + self::EXTRA_SUSPENDED_COUNT, count($users));
1187     }
1189     public function test_cron() {
1190         // First run cron so there are no messages waiting to be sent (from other tests).
1191         cron_setup_user();
1192         assign::cron();
1194         // Now create an assignment and add some feedback.
1195         $this->setUser($this->editingteachers[0]);
1196         $assign = $this->create_instance(array('sendstudentnotifications'=>1));
1198         // Simulate adding a grade.
1199         $this->setUser($this->teachers[0]);
1200         $data = new stdClass();
1201         $data->grade = '50.0';
1202         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
1203         $assign->testable_apply_grade_to_user($data, $this->students[1]->id, 0);
1205         $data->sendstudentnotifications = false;
1206         $assign->testable_apply_grade_to_user($data, $this->students[2]->id, 0);
1208         // Now run cron and see that one message was sent.
1209         $this->preventResetByRollback();
1210         $sink = $this->redirectMessages();
1211         cron_setup_user();
1212         $this->expectOutputRegex('/Done processing 2 assignment submissions/');
1213         assign::cron();
1215         $messages = $sink->get_messages();
1216         // The sent count should be 2, because the 3rd one was marked as do not send notifications.
1217         $this->assertEquals(2, count($messages));
1218         $this->assertEquals(1, $messages[0]->notification);
1219         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1221         // Regrading a grade causes a notification to the user.
1222         $data->sendstudentnotifications = true;
1223         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
1224         assign::cron();
1225         $messages = $sink->get_messages();
1226         $this->assertEquals(3, count($messages));
1227     }
1229     /**
1230      * Test delivery of grade notifications as controlled by marking workflow.
1231      */
1232     public function test_markingworkflow_cron() {
1233         // First run cron so there are no messages waiting to be sent (from other tests).
1234         cron_setup_user();
1235         assign::cron();
1237         // Now create an assignment with marking workflow enabled.
1238         $this->setUser($this->editingteachers[0]);
1239         $assign = $this->create_instance(array('sendstudentnotifications' => 1, 'markingworkflow' => 1));
1241         // Simulate adding a grade.
1242         $this->setUser($this->teachers[0]);
1243         $data = new stdClass();
1244         $data->grade = '50.0';
1246         // This student will not receive notification.
1247         $data->sendstudentnotifications = 1;
1248         $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE;
1249         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
1251         // This student will receive notification.
1252         $data->sendstudentnotifications = 1;
1253         $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1254         $assign->testable_apply_grade_to_user($data, $this->students[1]->id, 0);
1256         // Now run cron and see that one message was sent.
1257         $this->preventResetByRollback();
1258         $sink = $this->redirectMessages();
1259         cron_setup_user();
1260         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1261         assign::cron();
1263         $messages = $sink->get_messages();
1264         $this->assertEquals(1, count($messages));
1265         $this->assertEquals($messages[0]->useridto, $this->students[1]->id);
1266         $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1267     }
1269     public function test_cron_message_includes_courseid() {
1270         // First run cron so there are no messages waiting to be sent (from other tests).
1271         cron_setup_user();
1272         assign::cron();
1274         // Now create an assignment.
1275         $this->setUser($this->editingteachers[0]);
1276         $assign = $this->create_instance(array('sendstudentnotifications' => 1));
1278         // Simulate adding a grade.
1279         $this->setUser($this->teachers[0]);
1280         $data = new stdClass();
1281         $data->grade = '50.0';
1282         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
1284         $this->preventResetByRollback();
1285         $sink = $this->redirectEvents();
1286         $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1288         assign::cron();
1290         $events = $sink->get_events();
1291         // Two notifications are sent, one to student and one to teacher. This generates
1292         // four events:
1293         // core\event\notification_sent
1294         // core\event\notification_viewed
1295         // core\event\notification_sent
1296         // core\event\notification_viewed.
1297         $event = reset($events);
1298         $this->assertInstanceOf('\core\event\notification_sent', $event);
1299         $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1300         $sink->close();
1301     }
1303     public function test_is_graded() {
1304         $this->setUser($this->editingteachers[0]);
1305         $assign = $this->create_instance();
1307         // Simulate adding a grade.
1308         $this->setUser($this->teachers[0]);
1309         $data = new stdClass();
1310         $data->grade = '50.0';
1311         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
1313         $this->assertEquals(true, $assign->testable_is_graded($this->students[0]->id));
1314         $this->assertEquals(false, $assign->testable_is_graded($this->students[1]->id));
1315     }
1317     public function test_can_grade() {
1318         global $DB;
1320         $this->setUser($this->editingteachers[0]);
1321         $assign = $this->create_instance();
1323         $this->setUser($this->students[0]);
1324         $this->assertEquals(false, $assign->can_grade());
1325         $this->setUser($this->editingteachers[0]);
1326         $this->assertEquals(true, $assign->can_grade());
1327         $this->setUser($this->teachers[0]);
1328         $this->assertEquals(true, $assign->can_grade());
1330         // Test the viewgrades capability - without mod/assign:grade.
1331         $this->setUser($this->students[0]);
1332         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1333         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1334         $this->assertEquals(false, $assign->can_grade());
1335     }
1337     public function test_can_view_submission() {
1338         global $DB;
1340         $this->create_extra_users();
1341         $this->setUser($this->editingteachers[0]);
1342         $assign = $this->create_instance();
1344         $this->setUser($this->students[0]);
1345         $this->assertEquals(true, $assign->can_view_submission($this->students[0]->id));
1346         $this->assertEquals(false, $assign->can_view_submission($this->students[1]->id));
1347         $this->assertEquals(false, $assign->can_view_submission($this->teachers[0]->id));
1348         $this->setUser($this->teachers[0]);
1349         $this->assertEquals(true, $assign->can_view_submission($this->students[0]->id));
1350         $this->assertEquals(true, $assign->can_view_submission($this->students[1]->id));
1351         $this->assertEquals(true, $assign->can_view_submission($this->teachers[0]->id));
1352         $this->assertEquals(false, $assign->can_view_submission($this->extrasuspendedstudents[0]->id));
1353         $this->setUser($this->editingteachers[0]);
1354         $this->assertEquals(true, $assign->can_view_submission($this->students[0]->id));
1355         $this->assertEquals(true, $assign->can_view_submission($this->students[1]->id));
1356         $this->assertEquals(true, $assign->can_view_submission($this->teachers[0]->id));
1357         $this->assertEquals(true, $assign->can_view_submission($this->extrasuspendedstudents[0]->id));
1359         // Test the viewgrades capability - without mod/assign:grade.
1360         $this->setUser($this->students[0]);
1361         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1362         assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1363         $this->assertEquals(true, $assign->can_view_submission($this->students[0]->id));
1364         $this->assertEquals(true, $assign->can_view_submission($this->students[1]->id));
1365         $this->assertEquals(true, $assign->can_view_submission($this->teachers[0]->id));
1366         $this->assertEquals(false, $assign->can_view_submission($this->extrasuspendedstudents[0]->id));
1367     }
1370     public function test_update_submission() {
1371         $this->create_extra_users();
1372         $this->setUser($this->editingteachers[0]);
1373         $assign = $this->create_instance();
1375         $this->setUser($this->extrastudents[0]);
1376         $now = time();
1377         $submission = $assign->get_user_submission($this->extrastudents[0]->id, true);
1378         $assign->testable_update_submission($submission, $this->extrastudents[0]->id, true, false);
1380         $this->setUser($this->teachers[0]);
1381         // Verify the gradebook update.
1382         $gradinginfo = grade_get_grades($this->course->id,
1383                                         'mod',
1384                                         'assign',
1385                                         $assign->get_instance()->id,
1386                                         $this->extrastudents[0]->id);
1388         $this->assertEquals($this->extrastudents[0]->id,
1389                             $gradinginfo->items[0]->grades[$this->extrastudents[0]->id]->usermodified);
1391         // Now verify group assignments.
1392         $this->setUser($this->editingteachers[0]);
1393         $assign = $this->create_instance(array('teamsubmission'=>1));
1395         $this->setUser($this->extrastudents[0]);
1396         $now = time();
1397         $submission = $assign->get_group_submission($this->extrastudents[0]->id, 0, true);
1398         $assign->testable_update_submission($submission, $this->extrastudents[0]->id, true, true);
1400         // Check that at least 2 active members and 1 suspended member of the submission group had their submission updated.
1402         $this->setUser($this->editingteachers[0]);
1403         $gradinginfo = grade_get_grades($this->course->id,
1404                                         'mod',
1405                                         'assign',
1406                                         $assign->get_instance()->id,
1407                                         $this->extrastudents[0]->id);
1409         $this->assertEquals($this->extrastudents[0]->id,
1410                             $gradinginfo->items[0]->grades[$this->extrastudents[0]->id]->usermodified);
1412         $gradinginfo = grade_get_grades($this->course->id,
1413                                         'mod',
1414                                         'assign',
1415                                         $assign->get_instance()->id,
1416                                         $this->extrastudents[self::GROUP_COUNT]->id);
1418         $this->assertEquals($this->extrastudents[self::GROUP_COUNT]->id,
1419                             $gradinginfo->items[0]->grades[$this->extrastudents[self::GROUP_COUNT]->id]->usermodified);
1421         $gradinginfo = grade_get_grades($this->course->id,
1422                                         'mod',
1423                                         'assign',
1424                                         $assign->get_instance()->id,
1425                                         $this->extrasuspendedstudents[0]->id);
1426         $this->assertEquals($this->extrasuspendedstudents[0]->id,
1427                             $gradinginfo->items[0]->grades[$this->extrasuspendedstudents[0]->id]->usermodified);
1429         // Check the same with non-editing teacher and make sure submission is not updated for suspended user.
1430         $this->setUser($this->editingteachers[0]);
1431         $assign = $this->create_instance(array('teamsubmission'=>1));
1433         $this->setUser($this->extrastudents[1]);
1434         $now = time();
1435         $submission = $assign->get_group_submission($this->extrastudents[1]->id, 0, true);
1436         $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, true);
1438         $this->setUser($this->teachers[0]);
1439         $gradinginfo = grade_get_grades($this->course->id,
1440                                         'mod',
1441                                         'assign',
1442                                         $assign->get_instance()->id,
1443                                         $this->extrastudents[1]->id);
1445         $this->assertEquals($this->extrastudents[1]->id,
1446                             $gradinginfo->items[0]->grades[$this->extrastudents[1]->id]->usermodified);
1448         $gradinginfo = grade_get_grades($this->course->id,
1449                                         'mod',
1450                                         'assign',
1451                                         $assign->get_instance()->id,
1452                                         $this->extrastudents[self::GROUP_COUNT+1]->id);
1454         $this->assertEquals($this->extrastudents[self::GROUP_COUNT+1]->id,
1455                             $gradinginfo->items[0]->grades[$this->extrastudents[self::GROUP_COUNT+1]->id]->usermodified);
1457         $gradinginfo = grade_get_grades($this->course->id,
1458                                         'mod',
1459                                         'assign',
1460                                         $assign->get_instance()->id,
1461                                         $this->extrasuspendedstudents[1]->id);
1462         $this->assertEquals($this->extrasuspendedstudents[1]->id,
1463                             $gradinginfo->items[0]->grades[$this->extrasuspendedstudents[1]->id]->usermodified);
1465         // Now verify blind marking.
1466         $this->setUser($this->editingteachers[0]);
1467         $assign = $this->create_instance(array('blindmarking'=>1));
1469         $this->setUser($this->extrastudents[0]);
1470         $now = time();
1471         $submission = $assign->get_user_submission($this->extrastudents[0]->id, true);
1472         $assign->testable_update_submission($submission, $this->extrastudents[0]->id, true, false);
1474         $this->setUser($this->editingteachers[0]);
1475         $gradinginfo = grade_get_grades($this->course->id,
1476                                         'mod',
1477                                         'assign',
1478                                         $assign->get_instance()->id,
1479                                         $this->extrastudents[0]->id);
1481         $this->assertEquals(null, $gradinginfo->items[0]->grades[$this->extrastudents[0]->id]->datesubmitted);
1482     }
1484     public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
1485         global $PAGE;
1487         $this->create_extra_users();
1488         // Now verify group assignments.
1489         $this->setUser($this->editingteachers[0]);
1490         $assign = $this->create_instance(array('teamsubmission'=>1,
1491                                                'assignsubmission_onlinetext_enabled'=>1,
1492                                                'submissiondrafts'=>1,
1493                                                'requireallteammemberssubmit'=>1));
1494         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
1496         // Add a submission.
1497         $this->setUser($this->extrastudents[0]);
1498         $data = new stdClass();
1499         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
1500                                          'text'=>'Submission text',
1501                                          'format'=>FORMAT_MOODLE);
1503         $notices = array();
1504         $assign->save_submission($data, $notices);
1506         // Check we can see the submit button.
1507         $output = $assign->view_student_summary($this->extrastudents[0], true);
1508         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1510         $submission = $assign->get_group_submission($this->extrastudents[0]->id, 0, true);
1511         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1512         $assign->testable_update_submission($submission, $this->extrastudents[0]->id, true, true);
1514         // Check that the student does not see "Submit" button.
1515         $output = $assign->view_student_summary($this->extrastudents[0], true);
1516         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1518         // Change to another user in the same group.
1519         $this->setUser($this->extrastudents[self::GROUP_COUNT]);
1520         $output = $assign->view_student_summary($this->extrastudents[self::GROUP_COUNT], true);
1521         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1523         $submission = $assign->get_group_submission($this->extrastudents[self::GROUP_COUNT]->id, 0, true);
1524         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1525         $assign->testable_update_submission($submission, $this->extrastudents[self::GROUP_COUNT]->id, true, true);
1526         $output = $assign->view_student_summary($this->extrastudents[self::GROUP_COUNT], true);
1527         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1528     }
1530     public function test_group_submissions_submit_for_marking() {
1531         global $PAGE;
1533         $this->create_extra_users();
1534         // Now verify group assignments.
1535         $this->setUser($this->editingteachers[0]);
1536         $time = time();
1537         $assign = $this->create_instance(array('teamsubmission'=>1,
1538                                                'assignsubmission_onlinetext_enabled'=>1,
1539                                                'submissiondrafts'=>1,
1540                                                'requireallteammemberssubmit'=>0,
1541                                                'duedate' => $time - 2*24*60*60));
1542         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
1544         $this->setUser($this->extrastudents[0]);
1545         // Add a submission.
1546         $data = new stdClass();
1547         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
1548                                          'text'=>'Submission text',
1549                                          'format'=>FORMAT_MOODLE);
1551         $notices = array();
1552         $assign->save_submission($data, $notices);
1554         // Check we can see the submit button.
1555         $output = $assign->view_student_summary($this->extrastudents[0], true);
1556         $this->assertContains(get_string('submitassignment', 'assign'), $output);
1557         $this->assertContains(get_string('timeremaining', 'assign'), $output);
1558         $difftime = time() - $time;
1559         $this->assertContains(get_string('overdue', 'assign', format_time(2*24*60*60 + $difftime)), $output);
1561         $submission = $assign->get_group_submission($this->extrastudents[0]->id, 0, true);
1562         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1563         $assign->testable_update_submission($submission, $this->extrastudents[0]->id, true, true);
1565         // Check that the student does not see "Submit" button.
1566         $output = $assign->view_student_summary($this->extrastudents[0], true);
1567         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1569         // Change to another user in the same group.
1570         $this->setUser($this->extrastudents[self::GROUP_COUNT]);
1571         $output = $assign->view_student_summary($this->extrastudents[self::GROUP_COUNT], true);
1572         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1574         // Check that time remaining is not overdue.
1575         $this->assertContains(get_string('timeremaining', 'assign'), $output);
1576         $difftime = time() - $time;
1577         $this->assertContains(get_string('submittedlate', 'assign', format_time(2*24*60*60 + $difftime)), $output);
1579         $submission = $assign->get_group_submission($this->extrastudents[self::GROUP_COUNT]->id, 0, true);
1580         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1581         $assign->testable_update_submission($submission, $this->extrastudents[self::GROUP_COUNT]->id, true, true);
1582         $output = $assign->view_student_summary($this->extrastudents[self::GROUP_COUNT], true);
1583         $this->assertNotContains(get_string('submitassignment', 'assign'), $output);
1584     }
1586     public function test_submissions_open() {
1587         $this->setUser($this->editingteachers[0]);
1589         $now = time();
1590         $tomorrow = $now + 24*60*60;
1591         $oneweek = $now + 7*24*60*60;
1592         $yesterday = $now - 24*60*60;
1594         $assign = $this->create_instance();
1595         $this->assertEquals(true, $assign->testable_submissions_open($this->students[0]->id));
1597         $assign = $this->create_instance(array('duedate'=>$tomorrow));
1598         $this->assertEquals(true, $assign->testable_submissions_open($this->students[0]->id));
1600         $assign = $this->create_instance(array('duedate'=>$yesterday));
1601         $this->assertEquals(true, $assign->testable_submissions_open($this->students[0]->id));
1603         $assign = $this->create_instance(array('duedate'=>$yesterday, 'cutoffdate'=>$tomorrow));
1604         $this->assertEquals(true, $assign->testable_submissions_open($this->students[0]->id));
1606         $assign = $this->create_instance(array('duedate'=>$yesterday, 'cutoffdate'=>$yesterday));
1607         $this->assertEquals(false, $assign->testable_submissions_open($this->students[0]->id));
1609         $assign->testable_save_user_extension($this->students[0]->id, $tomorrow);
1610         $this->assertEquals(true, $assign->testable_submissions_open($this->students[0]->id));
1612         $assign = $this->create_instance(array('submissiondrafts'=>1));
1613         $this->assertEquals(true, $assign->testable_submissions_open($this->students[0]->id));
1615         $this->setUser($this->students[0]);
1616         $now = time();
1617         $submission = $assign->get_user_submission($this->students[0]->id, true);
1618         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1619         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
1620         $this->setUser($this->editingteachers[0]);
1621         $this->assertEquals(false, $assign->testable_submissions_open($this->students[0]->id));
1622     }
1624     public function test_get_graders() {
1625         $this->create_extra_users();
1626         $this->setUser($this->editingteachers[0]);
1628         // Create an assignment with no groups.
1629         $assign = $this->create_instance();
1630         $this->assertCount(self::DEFAULT_TEACHER_COUNT +
1631                            self::DEFAULT_EDITING_TEACHER_COUNT +
1632                            self::EXTRA_TEACHER_COUNT +
1633                            self::EXTRA_EDITING_TEACHER_COUNT,
1634                            $assign->testable_get_graders($this->students[0]->id));
1636         // Force create an assignment with SEPARATEGROUPS.
1637         $data = new stdClass();
1638         $data->courseid = $this->course->id;
1639         $data->name = 'Grouping';
1640         $groupingid = groups_create_grouping($data);
1641         groups_assign_grouping($groupingid, $this->groups[0]->id);
1642         $assign = $this->create_instance(array('groupingid' => $groupingid, 'groupmode' => SEPARATEGROUPS));
1644         $this->setUser($this->students[1]);
1645         $this->assertCount(4, $assign->testable_get_graders($this->students[0]->id));
1646         // Note the second student is in a group that is not in the grouping.
1647         // This means that we get all graders that are not in a group in the grouping.
1648         $this->assertCount(10, $assign->testable_get_graders($this->students[1]->id));
1649     }
1651     public function test_get_notified_users() {
1652         global $CFG, $DB;
1654         $capability = 'mod/assign:receivegradernotifications';
1655         $coursecontext = context_course::instance($this->course->id);
1656         $role = $DB->get_record('role', array('shortname' => 'teacher'));
1658         $this->create_extra_users();
1659         $this->setUser($this->editingteachers[0]);
1661         // Create an assignment with no groups.
1662         $assign = $this->create_instance();
1664         $this->assertCount(self::DEFAULT_TEACHER_COUNT +
1665                            self::DEFAULT_EDITING_TEACHER_COUNT +
1666                            self::EXTRA_TEACHER_COUNT +
1667                            self::EXTRA_EDITING_TEACHER_COUNT,
1668                            $assign->testable_get_notifiable_users($this->students[0]->id));
1670         // Change nonediting teachers role to not receive grader notifications.
1671         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
1673         $this->assertCount(self::DEFAULT_EDITING_TEACHER_COUNT +
1674                            self::EXTRA_EDITING_TEACHER_COUNT,
1675                            $assign->testable_get_notifiable_users($this->students[0]->id));
1677         // Reset nonediting teachers role to default.
1678         unassign_capability($capability, $role->id, $coursecontext);
1680         // Force create an assignment with SEPARATEGROUPS.
1681         $data = new stdClass();
1682         $data->courseid = $this->course->id;
1683         $data->name = 'Grouping';
1684         $groupingid = groups_create_grouping($data);
1685         groups_assign_grouping($groupingid, $this->groups[0]->id);
1686         $assign = $this->create_instance(array('groupingid' => $groupingid, 'groupmode' => SEPARATEGROUPS));
1688         $this->setUser($this->students[1]);
1689         $this->assertCount(4, $assign->testable_get_notifiable_users($this->students[0]->id));
1690         // Note the second student is in a group that is not in the grouping.
1691         // This means that we get all graders that are not in a group in the grouping.
1692         $this->assertCount(10, $assign->testable_get_notifiable_users($this->students[1]->id));
1694         // Change nonediting teachers role to not receive grader notifications.
1695         assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
1697         $this->assertCount(2, $assign->testable_get_notifiable_users($this->students[0]->id));
1698         // Note the second student is in a group that is not in the grouping.
1699         // This means that we get all graders that are not in a group in the grouping.
1700         $this->assertCount(5, $assign->testable_get_notifiable_users($this->students[1]->id));
1701     }
1703     public function test_group_members_only() {
1704         global $CFG;
1706         $this->setAdminUser();
1707         $this->create_extra_users();
1708         $CFG->enableavailability = true;
1709         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $this->course->id));
1710         groups_assign_grouping($grouping->id, $this->groups[0]->id);
1712         // Force create an assignment with SEPARATEGROUPS.
1713         $instance = $this->getDataGenerator()->create_module('assign', array('course'=>$this->course->id),
1714                 array('availability' => json_encode(\core_availability\tree::get_root_json(array(
1715                     \availability_grouping\condition::get_json()))),
1716                 'groupingid' => $grouping->id));
1718         $cm = get_coursemodule_from_instance('assign', $instance->id);
1719         $context = context_module::instance($cm->id);
1720         $assign = new testable_assign($context, $cm, $this->course);
1722         $this->setUser($this->teachers[0]);
1723         get_fast_modinfo($this->course, 0, true);
1724         $this->assertCount(5, $assign->list_participants(0, true));
1726     }
1728     public function test_get_uniqueid_for_user() {
1729         $this->setUser($this->editingteachers[0]);
1730         $assign = $this->create_instance();
1732         foreach ($this->students as $student) {
1733             $uniqueid = $assign->get_uniqueid_for_user($student->id);
1734             $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
1735         }
1736     }
1738     public function test_show_student_summary() {
1739         global $CFG, $PAGE;
1741         $this->setUser($this->editingteachers[0]);
1742         $assign = $this->create_instance();
1743         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
1745         // No feedback should be available because this student has not been graded.
1746         $this->setUser($this->students[0]);
1747         $output = $assign->view_student_summary($this->students[0], true);
1748         $this->assertEquals(false, strpos($output, 'Feedback'), 'Do not show feedback if there is no grade');
1749         // Simulate adding a grade.
1750         $this->setUser($this->teachers[0]);
1751         $data = new stdClass();
1752         $data->grade = '50.0';
1753         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
1755         // Now we should see the feedback.
1756         $this->setUser($this->students[0]);
1757         $output = $assign->view_student_summary($this->students[0], true);
1758         $this->assertNotEquals(false, strpos($output, 'Feedback'), 'Show feedback if there is a grade');
1760         // Now hide the grade in gradebook.
1761         $this->setUser($this->teachers[0]);
1762         require_once($CFG->libdir.'/gradelib.php');
1763         $gradeitem = new grade_item(array(
1764             'itemtype'      => 'mod',
1765             'itemmodule'    => 'assign',
1766             'iteminstance'  => $assign->get_instance()->id,
1767             'courseid'      => $this->course->id));
1769         $gradeitem->set_hidden(1, false);
1771         // No feedback should be available because the grade is hidden.
1772         $this->setUser($this->students[0]);
1773         $output = $assign->view_student_summary($this->students[0], true);
1774         $this->assertEquals(false, strpos($output, 'Feedback'), 'Do not show feedback if the grade is hidden in the gradebook');
1776         // Do the same but add feedback.
1777         $assign = $this->create_instance(array('assignfeedback_comments_enabled' => 1));
1779         $this->setUser($this->teachers[0]);
1780         $grade = $assign->get_user_grade($this->students[0]->id, true);
1781         $data = new stdClass();
1782         $data->assignfeedbackcomments_editor = array('text'=>'Tomato sauce',
1783                                          'format'=>FORMAT_MOODLE);
1784         $plugin = $assign->get_feedback_plugin_by_type('comments');
1785         $plugin->save($grade, $data);
1787         // Should have feedback but no grade.
1788         $this->setUser($this->students[0]);
1789         $output = $assign->view_student_summary($this->students[0], true);
1790         $this->assertNotEquals(false, strpos($output, 'Feedback'), 'Show feedback even if there is no grade');
1791         $this->assertEquals(false, strpos($output, 'Grade'), 'Do not show grade when there is no grade.');
1792         $this->assertEquals(false, strpos($output, 'Graded on'), 'Do not show graded date when there is no grade.');
1794         // Now hide the grade in gradebook.
1795         $this->setUser($this->teachers[0]);
1796         $gradeitem = new grade_item(array(
1797             'itemtype'      => 'mod',
1798             'itemmodule'    => 'assign',
1799             'iteminstance'  => $assign->get_instance()->id,
1800             'courseid'      => $this->course->id));
1802         $gradeitem->set_hidden(1, false);
1804         // No feedback should be available because the grade is hidden.
1805         $this->setUser($this->students[0]);
1806         $output = $assign->view_student_summary($this->students[0], true);
1807         $this->assertEquals(false, strpos($output, 'Feedback'), 'Do not show feedback if the grade is hidden in the gradebook');
1808     }
1810     public function test_attempt_reopen_method_manual() {
1811         global $PAGE;
1813         $this->setUser($this->editingteachers[0]);
1814         $assign = $this->create_instance(array('attemptreopenmethod'=>ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
1815                                                'maxattempts'=>3,
1816                                                'submissiondrafts'=>1,
1817                                                'assignsubmission_onlinetext_enabled'=>1));
1818         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
1820         // Student should be able to see an add submission button.
1821         $this->setUser($this->students[0]);
1822         $output = $assign->view_student_summary($this->students[0], true);
1823         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
1825         // Add a submission.
1826         $now = time();
1827         $submission = $assign->get_user_submission($this->students[0]->id, true);
1828         $data = new stdClass();
1829         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
1830                                          'text'=>'Submission text',
1831                                          'format'=>FORMAT_MOODLE);
1832         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1833         $plugin->save($submission, $data);
1835         // And now submit it for marking.
1836         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1837         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
1839         // Verify the student cannot make changes to the submission.
1840         $output = $assign->view_student_summary($this->students[0], true);
1841         $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
1843         // Mark the submission.
1844         $this->setUser($this->teachers[0]);
1845         $data = new stdClass();
1846         $data->grade = '50.0';
1847         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
1849         // Check the student can see the grade.
1850         $this->setUser($this->students[0]);
1851         $output = $assign->view_student_summary($this->students[0], true);
1852         $this->assertNotEquals(false, strpos($output, '50.0'));
1854         // Allow the student another attempt.
1855         $this->teachers[0]->ignoresesskey = true;
1856         $this->setUser($this->teachers[0]);
1857         $result = $assign->testable_process_add_attempt($this->students[0]->id);
1858         $this->assertEquals(true, $result);
1860         // Check that the previous attempt is now in the submission history table.
1861         $this->setUser($this->students[0]);
1862         $output = $assign->view_student_summary($this->students[0], true);
1863         // Need a better check.
1864         $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
1866         // Check that the student now has a button for Add a new attempt".
1867         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
1868         // Check that the student now does not have a button for Submit.
1869         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
1871         // Check that the student now has a submission history.
1872         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
1874         $this->setUser($this->teachers[0]);
1875         // Check that the grading table loads correctly and contains this user.
1876         // This is also testing that we do not get duplicate rows in the grading table.
1877         $gradingtable = new assign_grading_table($assign, 100, '', 0, true);
1878         $output = $assign->get_renderer()->render($gradingtable);
1879         $this->assertEquals(true, strpos($output, $this->students[0]->lastname));
1881         // Should be 1 not 2.
1882         $this->assertEquals(1, $assign->count_submissions());
1883         $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
1884         $this->assertEquals(0, $assign->count_submissions_need_grading());
1885         $this->assertEquals(1, $assign->count_grades());
1887         // Change max attempts to unlimited.
1888         $formdata = clone($assign->get_instance());
1889         $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
1890         $formdata->instance = $formdata->id;
1891         $assign->update_instance($formdata);
1893         // Mark the submission again.
1894         $data = new stdClass();
1895         $data->grade = '60.0';
1896         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 1);
1898         // Check the grade exists.
1899         $grades = $assign->get_user_grades_for_gradebook($this->students[0]->id);
1900         $this->assertEquals(60, (int)$grades[$this->students[0]->id]->rawgrade);
1902         // Check we can reopen still.
1903         $result = $assign->testable_process_add_attempt($this->students[0]->id);
1904         $this->assertEquals(true, $result);
1906         // Should no longer have a grade because there is no grade for the latest attempt.
1907         $grades = $assign->get_user_grades_for_gradebook($this->students[0]->id);
1908         $this->assertEmpty($grades);
1910     }
1912     /**
1913      * Test reopen behavior when in "Reopen until pass" mode.
1914      */
1915     public function test_attempt_reopen_method_untilpass() {
1916         global $PAGE;
1918         $this->setUser($this->editingteachers[0]);
1919         $assign = $this->create_instance(array('attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
1920                 'maxattempts' => 3,
1921                 'submissiondrafts' => 1,
1922                 'assignsubmission_onlinetext_enabled' => 1));
1923         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
1925         // Set grade to pass to 80.
1926         $gradeitem = $assign->get_grade_item();
1927         $gradeitem->gradepass = '80.0';
1928         $gradeitem->update();
1930         // Student should be able to see an add submission button.
1931         $this->setUser($this->students[0]);
1932         $output = $assign->view_student_summary($this->students[0], true);
1933         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
1935         // Add a submission.
1936         $now = time();
1937         $submission = $assign->get_user_submission($this->students[0]->id, true);
1938         $data = new stdClass();
1939         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1940                 'text' => 'Submission text',
1941                 'format' => FORMAT_MOODLE);
1942         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1943         $plugin->save($submission, $data);
1945         // And now submit it for marking.
1946         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1947         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
1949         // Verify the student cannot make a new attempt.
1950         $output = $assign->view_student_summary($this->students[0], true);
1951         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
1953         // Mark the submission as non-passing.
1954         $this->setUser($this->teachers[0]);
1955         $data = new stdClass();
1956         $data->grade = '50.0';
1957         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
1959         // Check the student can see the grade.
1960         $this->setUser($this->students[0]);
1961         $output = $assign->view_student_summary($this->students[0], true);
1962         $this->assertNotEquals(false, strpos($output, '50.0'));
1964         // Check that the student now has a button for Add a new attempt.
1965         $output = $assign->view_student_summary($this->students[0], true);
1966         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
1968         // Check that the student now does not have a button for Submit.
1969         $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
1971         // Check that the student now has a submission history.
1972         $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
1974         // Add a second submission.
1975         $now = time();
1976         $submission = $assign->get_user_submission($this->students[0]->id, true, 1);
1977         $data = new stdClass();
1978         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1979                 'text' => 'Submission text',
1980                 'format' => FORMAT_MOODLE);
1981         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1982         $plugin->save($submission, $data);
1984         // And now submit it for marking.
1985         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1986         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
1988         // Mark the submission as passing.
1989         $this->setUser($this->teachers[0]);
1990         $data = new stdClass();
1991         $data->grade = '80.0';
1992         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 1);
1994         // Check that the student does not have a button for Add a new attempt.
1995         $this->setUser($this->students[0]);
1996         $output = $assign->view_student_summary($this->students[0], true);
1997         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
1999         // Re-mark the submission as not passing.
2000         $this->setUser($this->teachers[0]);
2001         $data = new stdClass();
2002         $data->grade = '50.0';
2003         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 1);
2005         // Check that the student now has a button for Add a new attempt.
2006         $this->setUser($this->students[0]);
2007         $output = $assign->view_student_summary($this->students[0], true);
2008         $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2010         // Add a submission as a second student.
2011         $this->setUser($this->students[1]);
2012         $now = time();
2013         $submission = $assign->get_user_submission($this->students[1]->id, true);
2014         $data = new stdClass();
2015         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
2016                 'text' => 'Submission text',
2017                 'format' => FORMAT_MOODLE);
2018         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
2019         $plugin->save($submission, $data);
2021         // And now submit it for marking.
2022         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2023         $assign->testable_update_submission($submission, $this->students[1]->id, true, false);
2025         // Mark the submission as passing.
2026         $this->setUser($this->teachers[0]);
2027         $data = new stdClass();
2028         $data->grade = '100.0';
2029         $assign->testable_apply_grade_to_user($data, $this->students[1]->id, 0);
2031         // Check the student can see the grade.
2032         $this->setUser($this->students[1]);
2033         $output = $assign->view_student_summary($this->students[1], true);
2034         $this->assertNotEquals(false, strpos($output, '100.0'));
2036         // Check that the student does not have a button for Add a new attempt.
2037         $output = $assign->view_student_summary($this->students[1], true);
2038         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2040         // Set grade to pass to 0, so that no attempts should reopen.
2041         $gradeitem = $assign->get_grade_item();
2042         $gradeitem->gradepass = '0';
2043         $gradeitem->update();
2045         // Add another submission.
2046         $this->setUser($this->students[2]);
2047         $now = time();
2048         $submission = $assign->get_user_submission($this->students[2]->id, true);
2049         $data = new stdClass();
2050         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
2051                 'text' => 'Submission text',
2052                 'format' => FORMAT_MOODLE);
2053         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
2054         $plugin->save($submission, $data);
2056         // And now submit it for marking.
2057         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2058         $assign->testable_update_submission($submission, $this->students[2]->id, true, false);
2060         // Mark the submission as graded.
2061         $this->setUser($this->teachers[0]);
2062         $data = new stdClass();
2063         $data->grade = '0.0';
2064         $assign->testable_apply_grade_to_user($data, $this->students[2]->id, 0);
2066         // Check the student can see the grade.
2067         $this->setUser($this->students[2]);
2068         $output = $assign->view_student_summary($this->students[2], true);
2069         $this->assertNotEquals(false, strpos($output, '0.0'));
2071         // Check that the student does not have a button for Add a new attempt.
2072         $output = $assign->view_student_summary($this->students[2], true);
2073         $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2074     }
2077     public function test_markingworkflow() {
2078         global $PAGE;
2080         $this->setUser($this->editingteachers[0]);
2081         $assign = $this->create_instance(array('markingworkflow'=>1));
2082         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
2084         // Mark the submission and set to notmarked.
2085         $this->setUser($this->teachers[0]);
2086         $data = new stdClass();
2087         $data->grade = '50.0';
2088         $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED;
2089         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
2091         // Check the student can't see the grade.
2092         $this->setUser($this->students[0]);
2093         $output = $assign->view_student_summary($this->students[0], true);
2094         $this->assertEquals(false, strpos($output, '50.0'));
2096         // Make sure the grade isn't pushed to the gradebook.
2097         $grades = $assign->get_user_grades_for_gradebook($this->students[0]->id);
2098         $this->assertEmpty($grades);
2100         // Mark the submission and set to inmarking.
2101         $this->setUser($this->teachers[0]);
2102         $data = new stdClass();
2103         $data->grade = '50.0';
2104         $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_INMARKING;
2105         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
2107         // Check the student can't see the grade.
2108         $this->setUser($this->students[0]);
2109         $output = $assign->view_student_summary($this->students[0], true);
2110         $this->assertEquals(false, strpos($output, '50.0'));
2112         // Make sure the grade isn't pushed to the gradebook.
2113         $grades = $assign->get_user_grades_for_gradebook($this->students[0]->id);
2114         $this->assertEmpty($grades);
2116         // Mark the submission and set to readyforreview.
2117         $this->setUser($this->teachers[0]);
2118         $data = new stdClass();
2119         $data->grade = '50.0';
2120         $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW;
2121         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
2123         // Check the student can't see the grade.
2124         $this->setUser($this->students[0]);
2125         $output = $assign->view_student_summary($this->students[0], true);
2126         $this->assertEquals(false, strpos($output, '50.0'));
2128         // Make sure the grade isn't pushed to the gradebook.
2129         $grades = $assign->get_user_grades_for_gradebook($this->students[0]->id);
2130         $this->assertEmpty($grades);
2132         // Mark the submission and set to inreview.
2133         $this->setUser($this->teachers[0]);
2134         $data = new stdClass();
2135         $data->grade = '50.0';
2136         $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW;
2137         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
2139         // Check the student can't see the grade.
2140         $this->setUser($this->students[0]);
2141         $output = $assign->view_student_summary($this->students[0], true);
2142         $this->assertEquals(false, strpos($output, '50.0'));
2144         // Make sure the grade isn't pushed to the gradebook.
2145         $grades = $assign->get_user_grades_for_gradebook($this->students[0]->id);
2146         $this->assertEmpty($grades);
2148         // Mark the submission and set to readyforrelease.
2149         $this->setUser($this->teachers[0]);
2150         $data = new stdClass();
2151         $data->grade = '50.0';
2152         $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE;
2153         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
2155         // Check the student can't see the grade.
2156         $this->setUser($this->students[0]);
2157         $output = $assign->view_student_summary($this->students[0], true);
2158         $this->assertEquals(false, strpos($output, '50.0'));
2160         // Make sure the grade isn't pushed to the gradebook.
2161         $grades = $assign->get_user_grades_for_gradebook($this->students[0]->id);
2162         $this->assertEmpty($grades);
2164         // Mark the submission and set to released.
2165         $this->setUser($this->teachers[0]);
2166         $data = new stdClass();
2167         $data->grade = '50.0';
2168         $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
2169         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
2171         // Check the student can see the grade.
2172         $this->setUser($this->students[0]);
2173         $output = $assign->view_student_summary($this->students[0], true);
2174         $this->assertNotEquals(false, strpos($output, '50.0'));
2176         // Make sure the grade is pushed to the gradebook.
2177         $grades = $assign->get_user_grades_for_gradebook($this->students[0]->id);
2178         $this->assertEquals(50, (int)$grades[$this->students[0]->id]->rawgrade);
2179     }
2181     public function test_markerallocation() {
2182         global $PAGE;
2184         $this->setUser($this->editingteachers[0]);
2185         $assign = $this->create_instance(array('markingworkflow'=>1, 'markingallocation'=>1));
2186         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
2188         // Allocate marker to submission.
2189         $data = new stdClass();
2190         $data->allocatedmarker = $this->teachers[0]->id;
2191         $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0);
2193         // Check the allocated marker can view the submission.
2194         $this->setUser($this->teachers[0]);
2196         $users = $assign->list_participants(0, true);
2197         $user = reset($users);
2198         $this->assertEquals($this->students[0]->id, $user->id);
2200         $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2201         $context = context_module::instance($cm->id);
2202         $assign = new testable_assign($context, $cm, $this->course);
2203         // Check that other teachers can't view this submission.
2204         $this->setUser($this->teachers[1]);
2205         $users = $assign->list_participants(0, true);
2206         $this->assertEquals(0, count($users));
2207     }
2209     /**
2210      * @expectedException moodle_exception
2211      */
2212     public function test_teacher_submit_for_student() {
2213         global $PAGE;
2215         $this->preventResetByRollback();
2216         $sink = $this->redirectMessages();
2218         $this->setUser($this->editingteachers[0]);
2220         $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled'=>1, 'submissiondrafts'=>1));
2221         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
2223         $this->setUser($this->students[0]);
2224         // Simulate a submission.
2225         $data = new stdClass();
2226         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
2227                                          'text'=>'Student submission text',
2228                                          'format'=>FORMAT_MOODLE);
2230         $notices = array();
2231         $assign->save_submission($data, $notices);
2233         // Check that the submission text was saved.
2234         $output = $assign->view_student_summary($this->students[0], true);
2235         $this->assertContains('Student submission text', $output, 'Contains student submission text');
2237         // Check that a teacher teacher with the extra capability can edit a students submission.
2238         $this->setUser($this->teachers[0]);
2239         $data = new stdClass();
2240         $data->userid = $this->students[0]->id;
2241         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
2242                                          'text'=>'Teacher edited submission text',
2243                                          'format'=>FORMAT_MOODLE);
2245         // Add the required capability.
2246         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2247         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2248         role_assign($roleid, $this->teachers[0]->id, $assign->get_context()->id);
2249         accesslib_clear_all_caches_for_unit_testing();
2251         // Try to save the submission.
2252         $notices = array();
2253         $assign->save_submission($data, $notices);
2255         // Check that the teacher can submit the students work.
2256         $data = new stdClass();
2257         $data->userid = $this->students[0]->id;
2258         $notices = array();
2259         $assign->submit_for_grading($data, $notices);
2261         // Revert to draft so the student can edit it.
2262         $assign->revert_to_draft($this->students[0]->id);
2264         $this->setUser($this->students[0]);
2266         // Check that the submission text was saved.
2267         $output = $assign->view_student_summary($this->students[0], true);
2268         $this->assertContains('Teacher edited submission text', $output, 'Contains student submission text');
2270         // Check that the student can submit their work.
2271         $data = new stdClass();
2272         $assign->submit_for_grading($data, $notices);
2274         $output = $assign->view_student_summary($this->students[0], true);
2275         $this->assertNotContains(get_string('addsubmission', 'assign'), $output);
2277         // Set to a default editing teacher who should not be able to edit this submission.
2278         $this->setUser($this->editingteachers[1]);
2280         // Revert to draft so the submission is editable.
2281         $assign->revert_to_draft($this->students[0]->id);
2283         $data = new stdClass();
2284         $data->userid = $this->students[0]->id;
2285         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
2286                                          'text'=>'Teacher 2 edited submission text',
2287                                          'format'=>FORMAT_MOODLE);
2289         $notices = array();
2290         $assign->save_submission($data, $notices);
2292         $sink->close();
2293     }
2295     public function test_disable_submit_after_cutoff_date() {
2296         global $PAGE;
2298         $this->setUser($this->editingteachers[0]);
2299         $now = time();
2300         $tomorrow = $now + 24*60*60;
2301         $lastweek = $now - 7*24*60*60;
2302         $yesterday = $now - 24*60*60;
2304         $assign = $this->create_instance(array('duedate'=>$yesterday,
2305                                                'cutoffdate'=>$tomorrow,
2306                                                'assignsubmission_onlinetext_enabled'=>1));
2307         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
2309         // Student should be able to see an add submission button.
2310         $this->setUser($this->students[0]);
2311         $output = $assign->view_student_summary($this->students[0], true);
2312         $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2314         // Add a submission but don't submit now.
2315         $submission = $assign->get_user_submission($this->students[0]->id, true);
2316         $data = new stdClass();
2317         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
2318                                          'text'=>'Submission text',
2319                                          'format'=>FORMAT_MOODLE);
2320         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
2321         $plugin->save($submission, $data);
2323         // Create another instance with cut-off and due-date already passed.
2324         $this->setUser($this->editingteachers[0]);
2325         $now = time();
2326         $assign = $this->create_instance(array('duedate'=>$lastweek,
2327                                                'cutoffdate'=>$yesterday,
2328                                                'assignsubmission_onlinetext_enabled'=>1));
2330         $this->setUser($this->students[0]);
2331         $output = $assign->view_student_summary($this->students[0], true);
2332         $this->assertNotContains($output, get_string('editsubmission', 'assign'),
2333                                  'Should not be able to edit after cutoff date.');
2334         $this->assertNotContains($output, get_string('submitassignment', 'assign'),
2335                                  'Should not be able to submit after cutoff date.');
2336     }
2337     /**
2338      * Testing for submission comment plugin settings
2339      */
2340     public function test_submission_comment_plugin_settings() {
2341         global $CFG;
2343         $commentconfig = false;
2344         if (!empty($CFG->usecomments)) {
2345             $commentconfig = $CFG->usecomments;
2346         }
2348         $CFG->usecomments = true;
2349         $assign = $this->create_instance();
2350         $plugin = $assign->get_submission_plugin_by_type('comments');
2351         $this->assertEquals(1, $plugin->is_enabled('enabled'));
2353         $assign = $this->create_instance(array('assignsubmission_comments_enabled' => 0));
2354         $plugin = $assign->get_submission_plugin_by_type('comments');
2355         $this->assertEquals(1, $plugin->is_enabled('enabled'));
2357         $assign = $this->create_instance(array('assignsubmission_comments_enabled' => 1));
2358         $plugin = $assign->get_submission_plugin_by_type('comments');
2359         $this->assertEquals(1, $plugin->is_enabled('enabled'));
2361         $CFG->usecomments = false;
2362         $assign = $this->create_instance();
2363         $plugin = $assign->get_submission_plugin_by_type('comments');
2364         $this->assertEquals(0, $plugin->is_enabled('enabled'));
2366         $assign = $this->create_instance(array('assignsubmission_comments_enabled' => 0));
2367         $plugin = $assign->get_submission_plugin_by_type('comments');
2368         $this->assertEquals(0, $plugin->is_enabled('enabled'));
2370         $assign = $this->create_instance(array('assignsubmission_comments_enabled' => 1));
2371         $plugin = $assign->get_submission_plugin_by_type('comments');
2372         $this->assertEquals(0, $plugin->is_enabled('enabled'));
2374         $CFG->usecomments = $commentconfig;
2375     }
2377     /**
2378      * Testing for comment inline settings
2379      */
2380     public function test_feedback_comment_commentinline() {
2381         global $CFG;
2383         $sourcetext = "Hello!
2385 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
2387 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
2388 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
2390 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
2391 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
2392 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
2393 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
2394 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
2395 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
2396 ";
2398         // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
2399         $filteredtext = "Hello!
2401 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
2403 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
2404 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
2406 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
2407 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
2408 Internal link 1:
2409 Internal link 2:
2410 Anchor link 1:Link text
2411 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
2412 ";
2414         $this->setUser($this->editingteachers[0]);
2415         $params = array('assignsubmission_onlinetext_enabled' => 1,
2416                         'assignfeedback_comments_enabled' => 1,
2417                         'assignfeedback_comments_commentinline' => 1);
2418         $assign = $this->create_instance($params);
2420         $this->setUser($this->students[0]);
2421         // Add a submission but don't submit now.
2422         $submission = $assign->get_user_submission($this->students[0]->id, true);
2423         $data = new stdClass();
2425         // Test the internal link is stripped, but the external one is not.
2426         $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
2427                                          'text'=>$sourcetext,
2428                                          'format'=>FORMAT_MOODLE);
2430         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
2431         $plugin->save($submission, $data);
2433         $this->setUser($this->editingteachers[0]);
2435         $data = new stdClass();
2436         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2437         $pagination = array('userid'=>$this->students[0]->id,
2438                             'rownum'=>0,
2439                             'last'=>true,
2440                             'useridlistid' => $assign->get_useridlist_key_id(),
2441                             'attemptnumber'=>0);
2442         $formparams = array($assign, $data, $pagination);
2443         $mform = new mod_assign_grade_form(null, $formparams);
2445         $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
2446     }
2448     /**
2449      * Testing for feedback comment plugin settings
2450      */
2451     public function test_feedback_plugin_settings() {
2453         $assign = $this->create_instance();
2454         $plugin = $assign->get_feedback_plugin_by_type('comments');
2455         $this->assertEquals(0, $plugin->is_enabled('enabled'));
2457         $assign = $this->create_instance(array('assignfeedback_comments_enabled' => 0));
2458         $plugin = $assign->get_feedback_plugin_by_type('comments');
2459         $this->assertEquals(0, $plugin->is_enabled('enabled'));
2461         $assign = $this->create_instance(array('assignfeedback_comments_enabled' => 1));
2462         $plugin = $assign->get_feedback_plugin_by_type('comments');
2463         $this->assertEquals(1, $plugin->is_enabled('enabled'));
2464     }
2466     /**
2467      * Testing if gradebook feedback plugin is enabled.
2468      */
2469     public function test_is_gradebook_feedback_enabled() {
2470         $adminconfig = get_config('assign');
2471         $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
2473         // Create assignment with gradebook feedback enabled and grade = 0.
2474         $assign = $this->create_instance(array($gradebookplugin . '_enabled' => 1, 'grades' => 0));
2476         // Get gradebook feedback plugin.
2477         $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
2478         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
2479         $this->assertEquals(1, $plugin->is_enabled('enabled'));
2480         $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
2482         // Create assignment with gradebook feedback disabled and grade = 0.
2483         $assign = $this->create_instance(array($gradebookplugin . '_enabled' => 0, 'grades' => 0));
2484         $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
2485         $this->assertEquals(0, $plugin->is_enabled('enabled'));
2486     }
2488     /**
2489      * Testing can_edit_submission
2490      */
2491     public function test_can_edit_submission() {
2492         global $PAGE, $DB;
2493         $this->create_extra_users();
2495         $this->setAdminUser();
2496         // Create assignment (onlinetext).
2497         $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled'=>1, 'submissiondrafts'=>1));
2498         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
2500         // Check student can edit their own submission.
2501         $this->assertTrue($assign->can_edit_submission($this->students[0]->id, $this->students[0]->id));
2502         // Check student cannot edit others submission.
2503         $this->assertFalse($assign->can_edit_submission($this->students[0]->id, $this->students[1]->id));
2505         // Check teacher cannot (by default) edit a students submission.
2506         $this->assertFalse($assign->can_edit_submission($this->students[0]->id, $this->teachers[0]->id));
2508         // Add the required capability to edit a student submission.
2509         $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2510         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2511         role_assign($roleid, $this->teachers[0]->id, $assign->get_context()->id);
2512         accesslib_clear_all_caches_for_unit_testing();
2513         // Retest - should now have access.
2514         $this->assertTrue($assign->can_edit_submission($this->students[0]->id, $this->teachers[0]->id));
2516         // Force create an assignment with SEPARATEGROUPS.
2517         $data = new stdClass();
2518         $data->courseid = $this->course->id;
2519         $data->name = 'Grouping';
2520         $groupingid = groups_create_grouping($data);
2521         groups_assign_grouping($groupingid, $this->groups[0]->id);
2522         groups_assign_grouping($groupingid, $this->groups[1]->id);
2523         $assign = $this->create_instance(array('groupingid' => $groupingid, 'groupmode' => SEPARATEGROUPS));
2525         // Add the capability to the new assignment for extra students 0 and 1.
2526         assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2527         role_assign($roleid, $this->extrastudents[0]->id, $assign->get_context()->id);
2528         role_assign($roleid, $this->extrastudents[1]->id, $assign->get_context()->id);
2529         accesslib_clear_all_caches_for_unit_testing();
2531         // Verify the extra student does not have the capability to edit a submission not in their group.
2532         $this->assertFalse($assign->can_edit_submission($this->students[0]->id, $this->extrastudents[1]->id));
2533         // Verify the extra student does have the capability to edit a submission in their group.
2534         $this->assertTrue($assign->can_edit_submission($this->students[0]->id, $this->extrastudents[0]->id));
2536     }
2538     /**
2539      * Test if the view blind details capability works
2540      */
2541     public function test_can_view_blind_details() {
2542         global $DB;
2543         // Note: The shared setUp leads to terrible tests. Please don't use it.
2544         $roles = $DB->get_records('role', null, '', 'shortname, id');
2545         $course = $this->getDataGenerator()->create_course([]);
2547         $student = $this->students[0];// Get a student user.
2548         $this->getDataGenerator()->enrol_user($student->id,
2549                                               $course->id,
2550                                               $roles['student']->id);
2552         // Create a teacher. Shouldn't be able to view blind marking ID.
2553         $teacher = $this->getDataGenerator()->create_user();
2555         $this->getDataGenerator()->enrol_user($teacher->id,
2556                                               $course->id,
2557                                               $roles['teacher']->id);
2559         // Create a manager.. Should be able to view blind marking ID.
2560         $manager = $this->getDataGenerator()->create_user();
2561         $this->getDataGenerator()->enrol_user($manager->id,
2562                                               $course->id,
2563                                               $roles['manager']->id);
2565         // Generate blind marking assignment.
2566         $assign = $this->create_instance(array('course' => $course->id, 'blindmarking' => 1));
2567         $this->assertEquals(true, $assign->is_blind_marking());
2569         // Test student names are hidden to teacher.
2570         $this->setUser($teacher);
2571         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
2572         $output = $assign->get_renderer()->render($gradingtable);
2573         $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));    // "Participant" is somewhere on the page.
2574         $this->assertEquals(false, strpos($output, fullname($student)));    // Students full name doesn't appear.
2576         // Test student names are visible to manager.
2577         $this->setUser($manager);
2578         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
2579         $output = $assign->get_renderer()->render($gradingtable);
2580         $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
2581         $this->assertEquals(true, strpos($output, fullname($student)));
2582     }
2584     /**
2585      * Testing get_shared_group_members
2586      */
2587     public function test_get_shared_group_members() {
2588         $this->create_extra_users();
2589         $this->setAdminUser();
2591         // Force create an assignment with SEPARATEGROUPS.
2592         $data = new stdClass();
2593         $data->courseid = $this->course->id;
2594         $data->name = 'Grouping';
2595         $groupingid = groups_create_grouping($data);
2596         groups_assign_grouping($groupingid, $this->groups[0]->id);
2597         groups_assign_grouping($groupingid, $this->groups[1]->id);
2598         $assign = $this->create_instance(array('groupingid' => $groupingid, 'groupmode' => SEPARATEGROUPS));
2599         $cm = $assign->get_course_module();
2601         // Add the capability to access allgroups.
2602         $roleid = create_role('Access all groups role', 'accessallgroupsrole', '');
2603         assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $assign->get_context()->id);
2604         role_assign($roleid, $this->extrastudents[3]->id, $assign->get_context()->id);
2605         accesslib_clear_all_caches_for_unit_testing();
2607         // Get shared group members for students 0 and 1.
2608         $groupmembers = array();
2609         $groupmembers[0] = $assign->get_shared_group_members($cm, $this->students[0]->id);
2610         $groupmembers[1] = $assign->get_shared_group_members($cm, $this->students[1]->id);
2612         // They should share groups with extrastudents 0 and 1.
2613         $this->assertTrue(in_array($this->extrastudents[0]->id, $groupmembers[0]));
2614         $this->assertFalse(in_array($this->extrastudents[0]->id, $groupmembers[1]));
2615         $this->assertTrue(in_array($this->extrastudents[1]->id, $groupmembers[1]));
2616         $this->assertFalse(in_array($this->extrastudents[1]->id, $groupmembers[0]));
2618         // Lists of group members for students and extrastudents should be the same.
2619         $this->assertEquals($groupmembers[0], $assign->get_shared_group_members($cm, $this->extrastudents[0]->id));
2620         $this->assertEquals($groupmembers[1], $assign->get_shared_group_members($cm, $this->extrastudents[1]->id));
2622         // Get all group members for extrastudent 3 wich can access all groups.
2623         $allgroupmembers = $assign->get_shared_group_members($cm, $this->extrastudents[3]->id);
2625         // Extrastudent 3 should see students 0 and 1, extrastudent 0 and 1.
2626         $this->assertTrue(in_array($this->students[0]->id, $allgroupmembers));
2627         $this->assertTrue(in_array($this->students[1]->id, $allgroupmembers));
2628         $this->assertTrue(in_array($this->extrastudents[0]->id, $allgroupmembers));
2629         $this->assertTrue(in_array($this->extrastudents[1]->id , $allgroupmembers));
2630     }
2632     /**
2633      * Test get plugins file areas
2634      */
2635     public function test_get_plugins_file_areas() {
2636         $this->setUser($this->editingteachers[0]);
2637         $assign = $this->create_instance();
2639         // Test that all the submission and feedback plugins are returning the expected file aras.
2640         $usingfilearea = 0;
2641         $coreplugins = core_plugin_manager::standard_plugins_list('assignsubmission');
2642         foreach ($assign->get_submission_plugins() as $plugin) {
2643             $type = $plugin->get_type();
2644             if (!in_array($type, $coreplugins)) {
2645                 continue;
2646             }
2647             $fileareas = $plugin->get_file_areas();
2649             if ($type == 'onlinetext') {
2650                 $this->assertEquals(array('submissions_onlinetext' => 'Online text'), $fileareas);
2651                 $usingfilearea++;
2652             } else if ($type == 'file') {
2653                 $this->assertEquals(array('submission_files' => 'File submissions'), $fileareas);
2654                 $usingfilearea++;
2655             } else {
2656                 $this->assertEmpty($fileareas);
2657             }
2658         }
2659         $this->assertEquals(2, $usingfilearea);
2661         $usingfilearea = 0;
2662         $coreplugins = core_plugin_manager::standard_plugins_list('assignfeedback');
2663         foreach ($assign->get_feedback_plugins() as $plugin) {
2664             $type = $plugin->get_type();
2665             if (!in_array($type, $coreplugins)) {
2666                 continue;
2667             }
2668             $fileareas = $plugin->get_file_areas();
2670             if ($type == 'editpdf') {
2671                 $this->assertEquals(array('download' => 'Annotate PDF'), $fileareas);
2672                 $usingfilearea++;
2673             } else if ($type == 'file') {
2674                 $this->assertEquals(array('feedback_files' => 'Feedback files'), $fileareas);
2675                 $usingfilearea++;
2676             } else {
2677                 $this->assertEmpty($fileareas);
2678             }
2679         }
2680         $this->assertEquals(2, $usingfilearea);
2681     }
2683     /**
2684      * Test override exists
2685      *
2686      * This function needs to obey the group override logic as per the assign grading table and
2687      * the overview block.
2688      */
2689     public function test_override_exists() {
2690         global $DB;
2692         $this->setAdminUser();
2694         $course = $this->getDataGenerator()->create_course();
2696         // Create an assign instance.
2697         $assign = $this->create_instance(['course' => $course]);
2698         $assigninstance = $assign->get_instance();
2700         // Create users.
2701         $users = [
2702             'Only in group A'                     => $this->getDataGenerator()->create_user(),
2703             'Only in group B'                     => $this->getDataGenerator()->create_user(),
2704             'In group A and B (no user override)' => $this->getDataGenerator()->create_user(),
2705             'In group A and B (user override)'    => $this->getDataGenerator()->create_user(),
2706             'In no groups'                        => $this->getDataGenerator()->create_user()
2707         ];
2709         // Enrol users.
2710         foreach ($users as $user) {
2711             $this->getDataGenerator()->enrol_user($user->id, $course->id);
2712         }
2714         // Create groups.
2715         $groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2716         $groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2718         // Add members to groups.
2719         // Group A.
2720         $this->getDataGenerator()->create_group_member(
2721             ['groupid' => $groupa->id, 'userid' => $users['Only in group A']->id]);
2722         $this->getDataGenerator()->create_group_member(
2723             ['groupid' => $groupa->id, 'userid' => $users['In group A and B (no user override)']->id]);
2724         $this->getDataGenerator()->create_group_member(
2725             ['groupid' => $groupa->id, 'userid' => $users['In group A and B (user override)']->id]);
2727         // Group B.
2728         $this->getDataGenerator()->create_group_member(
2729             ['groupid' => $groupb->id, 'userid' => $users['Only in group B']->id]);
2730         $this->getDataGenerator()->create_group_member(
2731             ['groupid' => $groupb->id, 'userid' => $users['In group A and B (no user override)']->id]);
2732         $this->getDataGenerator()->create_group_member(
2733             ['groupid' => $groupb->id, 'userid' => $users['In group A and B (user override)']->id]);
2735         // Overrides for each of the groups, and a user override.
2736         $overrides = [
2737             // Override for group A, highest priority (numerically lowest sortorder).
2738             [
2739                 'assignid' => $assigninstance->id,
2740                 'groupid' => $groupa->id,
2741                 'userid' => null,
2742                 'sortorder' => 1,
2743                 'allowsubmissionsfromdate' => 1,
2744                 'duedate' => 2,
2745                 'cutoffdate' => 3
2746             ],
2747             // Override for group B, lower priority (numerically higher sortorder).
2748             [
2749                 'assignid' => $assigninstance->id,
2750                 'groupid' => $groupb->id,
2751                 'userid' => null,
2752                 'sortorder' => 2,
2753                 'allowsubmissionsfromdate' => 5,
2754                 'duedate' => 6,
2755                 'cutoffdate' => 6
2756             ],
2757             // User override.
2758             [
2759                 'assignid' => $assigninstance->id,
2760                 'groupid' => null,
2761                 'userid' => $users['In group A and B (user override)']->id,
2762                 'sortorder' => null,
2763                 'allowsubmissionsfromdate' => 7,
2764                 'duedate' => 8,
2765                 'cutoffdate' => 9
2766             ],
2767         ];
2769         // Kinda hacky, need to add the ID to the overrides in the above array
2770         // for later.
2771         foreach ($overrides as &$override) {
2772             $override['id'] = $DB->insert_record('assign_overrides', $override);
2773         }
2775         $returnedoverrides = array_reduce(array_keys($users), function($carry, $description) use ($users, $assign) {
2776             return $carry + ['For user ' . lcfirst($description) => $assign->override_exists($users[$description]->id)];
2777         }, []);
2779         // Test we get back the correct override from override_exists (== checks all object members match).
2780         // User only in group A should see the group A override.
2781         $this->assertTrue($returnedoverrides['For user only in group A'] == (object)$overrides[0]);
2782         // User only in group B should see the group B override.
2783         $this->assertTrue($returnedoverrides['For user only in group B'] == (object)$overrides[1]);
2784         // User in group A and B, with no user override should see the group A override
2785         // as it has higher priority (numerically lower sortorder).
2786         $this->assertTrue($returnedoverrides['For user in group A and B (no user override)'] == (object)$overrides[0]);
2787         // User in group A and B, with a user override should see the user override
2788         // as it has higher priority (numerically lower sortorder).
2789         $this->assertTrue($returnedoverrides['For user in group A and B (user override)'] == (object)$overrides[2]);
2790         // User with no overrides should get nothing.
2791         $this->assertNull($returnedoverrides['For user in no groups']->duedate);
2792         $this->assertNull($returnedoverrides['For user in no groups']->cutoffdate);
2793         $this->assertNull($returnedoverrides['For user in no groups']->allowsubmissionsfromdate);
2794     }
2796     /**
2797      * Test the quicksave grades processor
2798      */
2799     public function test_process_save_quick_grades() {
2800         $this->editingteachers[0]->ignoresesskey = true;
2801         $this->setUser($this->editingteachers[0]);
2803         $assign = $this->create_instance(array('attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL));
2805         // Initially grade the user.
2806         $grade = $assign->get_user_grade($this->students[0]->id, false);
2807         if (!$grade) {
2808             $grade = new stdClass();
2809             $grade->attemptnumber = '';
2810             $grade->timemodified = '';
2811         }
2812         $data = array(
2813             'grademodified_' . $this->students[0]->id => $grade->timemodified,
2814             'gradeattempt_' . $this->students[0]->id => $grade->attemptnumber,
2815             'quickgrade_' . $this->students[0]->id => '60.0'
2816         );
2817         $result = $assign->testable_process_save_quick_grades($data);
2818         $this->assertContains(get_string('quickgradingchangessaved', 'assign'), $result);
2819         $grade = $assign->get_user_grade($this->students[0]->id, false);
2820         $this->assertEquals('60.0', $grade->grade);
2822         // Attempt to grade with a past attempts grade info.
2823         $assign->testable_process_add_attempt($this->students[0]->id);
2824         $data = array(
2825             'grademodified_' . $this->students[0]->id => $grade->timemodified,
2826             'gradeattempt_' . $this->students[0]->id => $grade->attemptnumber,
2827             'quickgrade_' . $this->students[0]->id => '50.0'
2828         );
2829         $result = $assign->testable_process_save_quick_grades($data);
2830         $this->assertContains(get_string('errorrecordmodified', 'assign'), $result);
2831         $grade = $assign->get_user_grade($this->students[0]->id, false);
2832         $this->assertFalse($grade);
2834         // Attempt to grade a the attempt.
2835         $submission = $assign->get_user_submission($this->students[0]->id, false);
2836         $data = array(
2837             'grademodified_' . $this->students[0]->id => '',
2838             'gradeattempt_' . $this->students[0]->id => $submission->attemptnumber,
2839             'quickgrade_' . $this->students[0]->id => '40.0'
2840         );
2841         $result = $assign->testable_process_save_quick_grades($data);
2842         $this->assertContains(get_string('quickgradingchangessaved', 'assign'), $result);
2843         $grade = $assign->get_user_grade($this->students[0]->id, false);
2844         $this->assertEquals('40.0', $grade->grade);
2846         // Catch grade update conflicts.
2847         // Save old data for later.
2848         $pastdata = $data;
2849         // Update the grade the 'good' way.
2850         $data = array(
2851             'grademodified_' . $this->students[0]->id => $grade->timemodified,
2852             'gradeattempt_' . $this->students[0]->id => $grade->attemptnumber,
2853             'quickgrade_' . $this->students[0]->id => '30.0'
2854         );
2855         $result = $assign->testable_process_save_quick_grades($data);
2856         $this->assertContains(get_string('quickgradingchangessaved', 'assign'), $result);
2857         $grade = $assign->get_user_grade($this->students[0]->id, false);
2858         $this->assertEquals('30.0', $grade->grade);
2860         // Now update using 'old' data. Should fail.
2861         $result = $assign->testable_process_save_quick_grades($pastdata);
2862         $this->assertContains(get_string('errorrecordmodified', 'assign'), $result);
2863         $grade = $assign->get_user_grade($this->students[0]->id, false);
2864         $this->assertEquals('30.0', $grade->grade);
2865     }
2867     /**
2868      * Test updating activity completion when submitting an assessment.
2869      */
2870     public function test_update_activity_completion_records_solitary_submission() {
2871         $assign = $this->create_instance(array('grade' => 100,
2872                 'completion' => COMPLETION_TRACKING_AUTOMATIC,
2873                 'requireallteammemberssubmit' => 0));
2875         $cm = $assign->get_course_module();
2877         $student = $this->students[0];
2878         $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
2880         $this->setUser($student);
2882         // Simulate a submission.
2883         $data = new stdClass();
2884         $data->onlinetext_editor = array(
2885             'itemid' => file_get_unused_draft_itemid(),
2886             'text' => 'Student submission text',
2887             'format' => FORMAT_MOODLE
2888         );
2889         $completion = new completion_info($this->course);
2891         $notices = array();
2892         $assign->save_submission($data, $notices);
2894         $submission = $assign->get_user_submission($student->id, true);
2896         // Check that completion is not met yet.
2897         $completiondata = $completion->get_data($cm, false, $student->id);
2898         $this->assertEquals(0, $completiondata->completionstate);
2899         $assign->testable_update_activity_completion_records(0, 0, $submission,
2900                 $student->id, COMPLETION_COMPLETE, $completion);
2901         // Completion should now be met.
2902         $completiondata = $completion->get_data($cm, false, $student->id);
2903         $this->assertEquals(1, $completiondata->completionstate);
2904     }
2906     /**
2907      * Test updating activity completion when submitting an assessment.
2908      */
2909     public function test_update_activity_completion_records_team_submission() {
2910         $assign = $this->create_instance(array('grade' => 100,
2911                 'completion' => COMPLETION_TRACKING_AUTOMATIC,
2912                  'teamsubmission' => 1));
2914         $cm = $assign->get_course_module();
2916         $student1 = $this->students[0];
2917         $student2 = $this->students[1];
2918         $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
2920         // Put both users into a group.
2921         $group1 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
2922         $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $student1->id));
2923         $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $student2->id));
2925         $this->setUser($student1);
2927         // Simulate a submission.
2928         $data = new stdClass();
2929         $data->onlinetext_editor = array(
2930             'itemid' => file_get_unused_draft_itemid(),
2931             'text' => 'Student submission text',
2932             'format' => FORMAT_MOODLE
2933         );
2934         $completion = new completion_info($this->course);
2936         $notices = array();
2937         $assign->save_submission($data, $notices);
2939         $submission = $assign->get_user_submission($student1->id, true);
2940         $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2941         $submission->groupid = $group1->id;
2943         // Check that completion is not met yet.
2944         $completiondata = $completion->get_data($cm, false, $student1->id);
2945         $this->assertEquals(0, $completiondata->completionstate);
2946         $completiondata = $completion->get_data($cm, false, $student2->id);
2947         $this->assertEquals(0, $completiondata->completionstate);
2948         $assign->testable_update_activity_completion_records(1, 0, $submission, $student1->id,
2949                 COMPLETION_COMPLETE, $completion);
2950         // Completion should now be met.
2951         $completiondata = $completion->get_data($cm, false, $student1->id);
2952         $this->assertEquals(1, $completiondata->completionstate);
2953         $completiondata = $completion->get_data($cm, false, $student2->id);
2954         $this->assertEquals(1, $completiondata->completionstate);
2955     }
2957     public function get_assignments_with_rescaled_null_grades_provider() {
2958         return [
2959             'Negative less than one is errant' => [
2960                 'grade' => -0.64,
2961                 'count' => 1,
2962             ],
2963             'Negative more than one is errant' => [
2964                 'grade' => -30.18,
2965                 'count' => 1,
2966             ],
2967             'Negative one exactly is not errant' => [
2968                 'grade' => ASSIGN_GRADE_NOT_SET,
2969                 'count' => 0,
2970             ],
2971             'Positive grade is not errant' => [
2972                 'grade' => 1,
2973                 'count' => 0,
2974             ],
2975             'Large grade is not errant' => [
2976                 'grade' => 100,
2977                 'count' => 0,
2978             ],
2979             'Zero grade is not errant' => [
2980                 'grade' => 0,
2981                 'count' => 0,
2982             ],
2983         ];
2984     }
2986     /**
2987      * Test determining if the assignment as any null grades that were rescaled.
2988      * @dataProvider get_assignments_with_rescaled_null_grades_provider
2989      */
2990     public function test_get_assignments_with_rescaled_null_grades($grade, $count) {
2991         global $DB;
2993         $this->resetAfterTest();
2995         $assign = $this->create_instance(array('grade' => 100));
2997         // Try getting a student's grade. This will give a grade of -1.
2998         // Then we can override it with a bad negative grade.
2999         $assign->get_user_grade($this->students[0]->id, true);
3001         // Set the grade to something errant.
3002         $DB->set_field(
3003             'assign_grades',
3004             'grade',
3005             $grade,
3006             [
3007                 'userid' => $this->students[0]->id,
3008                 'assignment' => $assign->get_instance()->id,
3009             ]
3010         );
3012         $this->assertCount($count, get_assignments_with_rescaled_null_grades());
3013     }
3015     /**
3016      * Data provider for test_fix_null_grades
3017      * @return array[] Test data for test_fix_null_grades. Each element should contain grade, expectedcount and gradebookvalue
3018      */
3019     public function fix_null_grades_provider() {
3020         return [
3021             'Negative less than one is errant' => [
3022                 'grade' => -0.64,
3023                 'gradebookvalue' => null,
3024             ],
3025             'Negative more than one is errant' => [
3026                 'grade' => -30.18,
3027                 'gradebookvalue' => null,
3028             ],
3029             'Negative one exactly is not errant, but shouldn\'t be pushed to gradebook' => [
3030                 'grade' => ASSIGN_GRADE_NOT_SET,
3031                 'gradebookvalue' => null,
3032             ],
3033             'Positive grade is not errant' => [
3034                 'grade' => 1,
3035                 'gradebookvalue' => 1,
3036             ],
3037             'Large grade is not errant' => [
3038                 'grade' => 100,
3039                 'gradebookvalue' => 100,
3040             ],
3041             'Zero grade is not errant' => [
3042                 'grade' => 0,
3043                 'gradebookvalue' => 0,
3044             ],
3045         ];
3046     }
3048     /**
3049      * Test fix_null_grades
3050      * @param number $grade The grade we should set in the assign grading table.
3051      * @param number $expectedcount The finalgrade we expect in the gradebook after fixing the grades.
3052      * @dataProvider fix_null_grades_provider
3053      */
3054     public function test_fix_null_grades($grade, $gradebookvalue) {
3055         global $DB;
3057         $this->resetAfterTest();
3059         $studentid = $this->students[0]->id;
3061         $assign = $this->create_instance();
3063         // Try getting a student's grade. This will give a grade of -1.
3064         // Then we can override it with a bad negative grade.
3065         $assign->get_user_grade($studentid, true);
3067         // Set the grade to something errant.
3068         $DB->set_field(
3069             'assign_grades',
3070             'grade',
3071             $grade,
3072             [
3073                 'userid' => $studentid,
3074                 'assignment' => $assign->get_instance()->id,
3075             ]
3076         );
3077         $assign->grade = $grade;
3078         $assigntemp = clone $assign->get_instance();
3079         $assigntemp->cmidnumber = $assign->get_course_module()->idnumber;
3080         assign_update_grades($assigntemp);
3082         // Check that the gradebook was updated with the assign grade. So we can guarentee test results later on.
3083         $expectedgrade = $grade == -1 ? null : $grade; // Assign sends null to the gradebook for -1 grades.
3084         $gradegrade = grade_grade::fetch(array('userid' => $studentid, 'itemid' => $assign->get_grade_item()->id));
3085         $this->assertEquals($expectedgrade, $gradegrade->rawgrade);
3087         // Call fix_null_grades().
3088         $method = new ReflectionMethod(assign::class, 'fix_null_grades');
3089         $method->setAccessible(true);
3090         $result = $method->invoke($assign);
3092         $this->assertSame(true, $result);
3094         $gradegrade = grade_grade::fetch(array('userid' => $studentid, 'itemid' => $assign->get_grade_item()->id));
3096         // Check that the grade was updated in the gradebook by fix_null_grades.
3097         $this->assertEquals($gradebookvalue, $gradegrade->finalgrade);
3098     }
3100     /**
3101      * Test grade override displays 'Graded' for students
3102      */
3103     public function test_grade_submission_override() {
3104         global $DB, $PAGE, $OUTPUT;
3106         $this->setUser($this->editingteachers[0]);
3107         $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled' => 1));
3109         $studentid = $this->students[0]->id;
3111         // Simulate adding a grade.
3112         $this->setUser($this->teachers[0]);
3113         $data = new stdClass();
3114         $data->grade = '50.0';
3115         $assign->testable_apply_grade_to_user($data, $studentid, 0);
3117         // Set grade override.
3118         $gradegrade = grade_grade::fetch(array('userid' => $studentid, 'itemid' => $assign->get_grade_item()->id));
3120         // Check that grade submission is not overridden yet.
3121         $this->assertEquals(false, $gradegrade->is_overridden());
3123         // Simulate a submission.
3124         $this->setUser($this->students[0]);
3125         $submission = $assign->get_user_submission($studentid, true);
3127         $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
3129         // Set override grade grade, and check that grade submission has been overridden.
3130         $gradegrade->set_overridden(true);
3131         $this->assertEquals(true, $gradegrade->is_overridden());
3133         // Check that submissionslocked message 'This assignment is not accepting submissions' does not appear for student.
3134         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
3135         $output = $assign->get_renderer()->render($gradingtable);
3136         $this->assertContains(get_string('submissionstatus_', 'assign'), $output);
3138         $assignsubmissionstatus = $assign->get_assign_submission_status_renderable($this->students[0], true);
3139         $output2 = $assign->get_renderer()->render($assignsubmissionstatus);
3141         // Check that submissionslocked 'This assignment is not accepting submissions' message does not appear for student.
3142         $this->assertNotContains(get_string('submissionslocked', 'assign'), $output2);
3143         // Check that submissionstatus_marked 'Graded' message does appear for student.
3144         $this->assertContains(get_string('submissionstatus_marked', 'assign'), $output2);
3145     }