MDL-71036 phpunit: assertContains() now performs strict comparison
[moodle.git] / mod / assign / tests / locallib_test.php
blob77d260c78856f6e440dff9b8da3d1fe555bcde8a
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.
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.
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
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
30 require_once($CFG->dirroot . '/mod/assign/locallib.php');
31 require_once($CFG->dirroot . '/mod/assign/upgradelib.php');
32 require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
34 /**
35 * Unit tests for (some of) mod/assign/locallib.php.
37 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 class mod_assign_locallib_testcase extends advanced_testcase {
42 // Use the generator helper.
43 use mod_assign_test_generator;
45 public function test_return_links() {
46 global $PAGE;
48 $this->resetAfterTest();
49 $course = $this->getDataGenerator()->create_course();
51 $assign = $this->create_instance($course);
52 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
54 $assign->register_return_link('RETURNACTION', ['param' => 1]);
55 $this->assertEquals('RETURNACTION', $assign->get_return_action());
56 $this->assertEquals(['param' => 1], $assign->get_return_params());
59 public function test_get_feedback_plugins() {
60 $this->resetAfterTest();
61 $course = $this->getDataGenerator()->create_course();
62 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
64 $this->setUser($teacher);
65 $assign = $this->create_instance($course);
66 $installedplugins = array_keys(core_component::get_plugin_list('assignfeedback'));
68 foreach ($assign->get_feedback_plugins() as $plugin) {
69 $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins');
73 public function test_get_submission_plugins() {
74 $this->resetAfterTest();
75 $course = $this->getDataGenerator()->create_course();
76 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
78 $this->setUser($teacher);
79 $assign = $this->create_instance($course);
80 $installedplugins = array_keys(core_component::get_plugin_list('assignsubmission'));
82 foreach ($assign->get_submission_plugins() as $plugin) {
83 $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins');
87 public function test_is_blind_marking() {
88 $this->resetAfterTest();
89 $course = $this->getDataGenerator()->create_course();
90 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
91 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
93 $this->setUser($teacher);
94 $assign = $this->create_instance($course, ['blindmarking' => 1]);
95 $this->assertEquals(true, $assign->is_blind_marking());
97 // Test cannot see student names.
98 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
99 $output = $assign->get_renderer()->render($gradingtable);
100 $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
102 // Test students cannot reveal identities.
103 $nopermission = false;
104 $student->ignoresesskey = true;
105 $this->setUser($student);
106 $this->expectException('required_capability_exception');
107 $assign->reveal_identities();
108 $student->ignoresesskey = false;
110 // Test teachers cannot reveal identities.
111 $nopermission = false;
112 $teacher->ignoresesskey = true;
113 $this->setUser($teacher);
114 $this->expectException('required_capability_exception');
115 $assign->reveal_identities();
116 $teacher->ignoresesskey = false;
118 // Test sesskey is required.
119 $this->setUser($teacher);
120 $this->expectException('moodle_exception');
121 $assign->reveal_identities();
123 // Test editingteacher can reveal identities if sesskey is ignored.
124 $teacher->ignoresesskey = true;
125 $this->setUser($teacher);
126 $assign->reveal_identities();
127 $this->assertEquals(false, $assign->is_blind_marking());
128 $teacher->ignoresesskey = false;
130 // Test student names are visible.
131 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
132 $output = $assign->get_renderer()->render($gradingtable);
133 $this->assertEquals(false, strpos($output, get_string('hiddenuser', 'assign')));
135 // Set this back to default.
136 $teacher->ignoresesskey = false;
140 * Data provider for test_get_assign_perpage
142 * @return array Provider data
144 public function get_assign_perpage_provider() {
145 return array(
146 array(
147 'maxperpage' => -1,
148 'userprefs' => array(
149 -1 => -1,
150 10 => 10,
151 20 => 20,
152 50 => 50,
155 array(
156 'maxperpage' => 15,
157 'userprefs' => array(
158 -1 => 15,
159 10 => 10,
160 20 => 15,
161 50 => 15,
168 * Test maxperpage
170 * @dataProvider get_assign_perpage_provider
171 * @param integer $maxperpage site config value
172 * @param array $userprefs Array of user preferences and expected page sizes
174 public function test_get_assign_perpage($maxperpage, $userprefs) {
175 $this->resetAfterTest();
176 $course = $this->getDataGenerator()->create_course();
177 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
178 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
180 $this->setUser($teacher);
181 $assign = $this->create_instance($course);
183 set_config('maxperpage', $maxperpage, 'assign');
184 set_user_preference('assign_perpage', null);
185 $this->assertEquals(10, $assign->get_assign_perpage());
186 foreach ($userprefs as $pref => $perpage) {
187 set_user_preference('assign_perpage', $pref);
188 $this->assertEquals($perpage, $assign->get_assign_perpage());
193 * Test filter by requires grading.
195 * This is specifically checking an assignment with no grade to make sure we do not
196 * get an exception thrown when rendering the grading table for this type of assignment.
198 public function test_gradingtable_filter_by_requiresgrading_no_grade() {
199 global $PAGE;
201 $this->resetAfterTest();
203 $course = $this->getDataGenerator()->create_course();
204 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
205 $this->setUser($teacher);
206 $assign = $this->create_instance($course, [
207 'assignsubmission_onlinetext_enabled' => 1,
208 'assignfeedback_comments_enabled' => 0,
209 'grade' => GRADE_TYPE_NONE
212 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
213 'id' => $assign->get_course_module()->id,
214 'action' => 'grading',
215 )));
217 // Render the table with the requires grading filter.
218 $gradingtable = new assign_grading_table($assign, 1, ASSIGN_FILTER_REQUIRE_GRADING, 0, true);
219 $output = $assign->get_renderer()->render($gradingtable);
221 // Test that the filter function does not throw errors for assignments with no grade.
222 $this->assertStringContainsString(get_string('nothingtodisplay'), $output);
227 * Test submissions with extension date.
229 public function test_gradingtable_extension_due_date() {
230 global $PAGE;
232 $this->resetAfterTest();
233 $course = $this->getDataGenerator()->create_course();
234 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
235 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
237 // Setup the assignment.
238 $this->setUser($teacher);
239 $time = time();
240 $assign = $this->create_instance($course, [
241 'assignsubmission_onlinetext_enabled' => 1,
242 'duedate' => time() - (4 * DAYSECS),
244 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
245 'id' => $assign->get_course_module()->id,
246 'action' => 'grading',
247 )));
249 // Check that the assignment is late.
250 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
251 $output = $assign->get_renderer()->render($gradingtable);
252 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
253 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS))), $output);
255 // Grant an extension.
256 $extendedtime = $time + (2 * DAYSECS);
257 $assign->testable_save_user_extension($student->id, $extendedtime);
258 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
259 $output = $assign->get_renderer()->render($gradingtable);
260 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
261 $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
263 // Simulate a submission.
264 $this->setUser($student);
265 $submission = $assign->get_user_submission($student->id, true);
266 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
267 $assign->testable_update_submission($submission, $student->id, true, false);
268 $data = new stdClass();
269 $data->onlinetext_editor = [
270 'itemid' => file_get_unused_draft_itemid(),
271 'text' => 'Submission text',
272 'format' => FORMAT_MOODLE,
274 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
275 $plugin->save($submission, $data);
277 // Verify output.
278 $this->setUser($teacher);
279 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
280 $output = $assign->get_renderer()->render($gradingtable);
281 $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
282 $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
286 * Test that late submissions with extension date calculate correctly.
288 public function test_gradingtable_extension_date_calculation_for_lateness() {
289 global $PAGE;
291 $this->resetAfterTest();
292 $course = $this->getDataGenerator()->create_course();
293 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
294 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
296 // Setup the assignment.
297 $this->setUser($teacher);
298 $time = time();
299 $assign = $this->create_instance($course, [
300 'assignsubmission_onlinetext_enabled' => 1,
301 'duedate' => time() - (4 * DAYSECS),
303 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
304 'id' => $assign->get_course_module()->id,
305 'action' => 'grading',
306 )));
308 // Check that the assignment is late.
309 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
310 $output = $assign->get_renderer()->render($gradingtable);
311 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
312 $difftime = time() - $time;
313 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
315 // Grant an extension that is in the past.
316 $assign->testable_save_user_extension($student->id, $time - (2 * DAYSECS));
317 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
318 $output = $assign->get_renderer()->render($gradingtable);
319 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
320 $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
321 $difftime = time() - $time;
322 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
324 // Simulate a submission.
325 $this->setUser($student);
326 $submission = $assign->get_user_submission($student->id, true);
327 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
328 $assign->testable_update_submission($submission, $student->id, true, false);
329 $data = new stdClass();
330 $data->onlinetext_editor = [
331 'itemid' => file_get_unused_draft_itemid(),
332 'text' => 'Submission text',
333 'format' => FORMAT_MOODLE,
335 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
336 $plugin->save($submission, $data);
337 $submittedtime = time();
339 // Verify output.
340 $this->setUser($teacher);
341 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
342 $output = $assign->get_renderer()->render($gradingtable);
343 $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
344 $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
346 $difftime = $submittedtime - $time;
347 $this->assertStringContainsString(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
350 public function test_gradingtable_status_rendering() {
351 global $PAGE;
353 $this->resetAfterTest();
354 $course = $this->getDataGenerator()->create_course();
355 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
356 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
358 // Setup the assignment.
359 $this->setUser($teacher);
360 $time = time();
361 $assign = $this->create_instance($course, [
362 'assignsubmission_onlinetext_enabled' => 1,
363 'duedate' => $time - (4 * DAYSECS),
365 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
366 'id' => $assign->get_course_module()->id,
367 'action' => 'grading',
368 )));
370 // Check that the assignment is late.
371 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
372 $output = $assign->get_renderer()->render($gradingtable);
373 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
374 $difftime = time() - $time;
375 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
377 // Simulate a student viewing the assignment without submitting.
378 $this->setUser($student);
379 $submission = $assign->get_user_submission($student->id, true);
380 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
381 $assign->testable_update_submission($submission, $student->id, true, false);
382 $submittedtime = time();
384 // Verify output.
385 $this->setUser($teacher);
386 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
387 $output = $assign->get_renderer()->render($gradingtable);
388 $difftime = $submittedtime - $time;
389 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
391 $document = new DOMDocument();
392 @$document->loadHTML($output);
393 $xpath = new DOMXPath($document);
394 $this->assertEmpty($xpath->evaluate('string(//td[@id="mod_assign_grading-' . $assign->get_context()->id. '_r0_c8"])'));
398 * Check that group submission information is rendered correctly in the
399 * grading table.
401 public function test_gradingtable_group_submissions_rendering() {
402 global $PAGE;
404 $this->resetAfterTest();
405 $course = $this->getDataGenerator()->create_course();
406 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
408 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
409 groups_add_member($group, $teacher);
411 $students = [];
413 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
414 $students[] = $student;
415 groups_add_member($group, $student);
417 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
418 $students[] = $student;
419 groups_add_member($group, $student);
421 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
422 $students[] = $student;
423 groups_add_member($group, $student);
425 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
426 $students[] = $student;
427 groups_add_member($group, $student);
429 // Verify group assignments.
430 $this->setUser($teacher);
431 $assign = $this->create_instance($course, [
432 'teamsubmission' => 1,
433 'assignsubmission_onlinetext_enabled' => 1,
434 'submissiondrafts' => 1,
435 'requireallteammemberssubmit' => 0,
437 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
438 'id' => $assign->get_course_module()->id,
439 'action' => 'grading',
440 )));
442 // Add a submission.
443 $this->setUser($student);
444 $data = new stdClass();
445 $data->onlinetext_editor = [
446 'itemid' => file_get_unused_draft_itemid(),
447 'text' => 'Submission text',
448 'format' => FORMAT_MOODLE,
450 $notices = array();
451 $assign->save_submission($data, $notices);
453 $submission = $assign->get_group_submission($student->id, 0, true);
454 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
455 $assign->testable_update_submission($submission, $student->id, true, true);
457 // Check output.
458 $this->setUser($teacher);
459 $gradingtable = new assign_grading_table($assign, 4, '', 0, true);
460 $output = $assign->get_renderer()->render($gradingtable);
461 $document = new DOMDocument();
462 @$document->loadHTML($output);
463 $xpath = new DOMXPath($document);
465 // The XPath expression is based on the unique ID of the table.
466 $xpathuniqueidroot = 'mod_assign_grading-' . $assign->get_context()->id;
468 // Check status.
469 $this->assertSame(get_string('submissionstatus_submitted', 'assign'),
470 $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c4"]/div[@class="submissionstatussubmitted"])'));
471 $this->assertSame(get_string('submissionstatus_submitted', 'assign'),
472 $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c4"]/div[@class="submissionstatussubmitted"])'));
474 // Check submission last modified date
475 $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c8"])')));
476 $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c8"])')));
478 // Check group.
479 $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c5"])'));
480 $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c5"])'));
482 // Check submission text.
483 $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c9"]/div/div)'));
484 $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c9"]/div/div)'));
486 // Check comments can be made.
487 $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r0_c10"]//textarea)'));
488 $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r3_c10"]//textarea)'));
491 public function test_show_intro() {
492 $this->resetAfterTest();
493 $course = $this->getDataGenerator()->create_course();
494 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
496 // Test whether we are showing the intro at the correct times.
497 $this->setUser($teacher);
498 $assign = $this->create_instance($course, ['alwaysshowdescription' => 1]);
500 $this->assertEquals(true, $assign->testable_show_intro());
502 $tomorrow = time() + DAYSECS;
504 $assign = $this->create_instance($course, [
505 'alwaysshowdescription' => 0,
506 'allowsubmissionsfromdate' => $tomorrow,
508 $this->assertEquals(false, $assign->testable_show_intro());
509 $yesterday = time() - DAYSECS;
510 $assign = $this->create_instance($course, [
511 'alwaysshowdescription' => 0,
512 'allowsubmissionsfromdate' => $yesterday,
514 $this->assertEquals(true, $assign->testable_show_intro());
517 public function test_has_submissions_or_grades() {
518 $this->resetAfterTest();
519 $course = $this->getDataGenerator()->create_course();
520 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
521 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
523 $this->setUser($teacher);
524 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
525 $instance = $assign->get_instance();
527 // Should start empty.
528 $this->assertEquals(false, $assign->has_submissions_or_grades());
530 // Simulate a submission.
531 $this->setUser($student);
532 $submission = $assign->get_user_submission($student->id, true);
534 // The submission is still new.
535 $this->assertEquals(false, $assign->has_submissions_or_grades());
537 // Submit the submission.
538 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
539 $assign->testable_update_submission($submission, $student->id, true, false);
540 $data = new stdClass();
541 $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
542 'text'=>'Submission text',
543 'format'=>FORMAT_MOODLE);
544 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
545 $plugin->save($submission, $data);
547 // Now test again.
548 $this->assertEquals(true, $assign->has_submissions_or_grades());
551 public function test_delete_grades() {
552 $this->resetAfterTest();
553 $course = $this->getDataGenerator()->create_course();
554 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
555 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
557 $this->setUser($teacher);
558 $assign = $this->create_instance($course);
560 // Simulate adding a grade.
561 $this->setUser($teacher);
562 $data = new stdClass();
563 $data->grade = '50.0';
564 $assign->testable_apply_grade_to_user($data, $student->id, 0);
566 // Now see if the data is in the gradebook.
567 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
569 $this->assertNotEquals(0, count($gradinginfo->items));
571 $assign->testable_delete_grades();
572 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
574 $this->assertEquals(0, count($gradinginfo->items));
577 public function test_delete_instance() {
578 $this->resetAfterTest();
579 $course = $this->getDataGenerator()->create_course();
580 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
581 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
583 $this->setUser($teacher);
584 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
586 // Simulate adding a grade.
587 $this->setUser($teacher);
588 $data = new stdClass();
589 $data->grade = '50.0';
590 $assign->testable_apply_grade_to_user($data, $student->id, 0);
592 // Simulate a submission.
593 $this->add_submission($student, $assign);
595 // Now try and delete.
596 $this->setUser($teacher);
597 $this->assertEquals(true, $assign->delete_instance());
600 public function test_reset_userdata() {
601 global $DB;
603 $this->resetAfterTest();
604 $course = $this->getDataGenerator()->create_course();
605 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
606 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
608 $now = time();
609 $this->setUser($teacher);
610 $assign = $this->create_instance($course, [
611 'assignsubmission_onlinetext_enabled' => 1,
612 'duedate' => $now,
615 // Simulate adding a grade.
616 $this->add_submission($student, $assign);
617 $this->submit_for_grading($student, $assign);
618 $this->mark_submission($teacher, $assign, $student, 50.0);
620 // Simulate a submission.
621 $this->setUser($student);
622 $submission = $assign->get_user_submission($student->id, true);
623 $data = new stdClass();
624 $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
625 'text'=>'Submission text',
626 'format'=>FORMAT_MOODLE);
627 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
628 $plugin->save($submission, $data);
630 $this->assertEquals(true, $assign->has_submissions_or_grades());
631 // Now try and reset.
632 $data = new stdClass();
633 $data->reset_assign_submissions = 1;
634 $data->reset_gradebook_grades = 1;
635 $data->reset_assign_user_overrides = 1;
636 $data->reset_assign_group_overrides = 1;
637 $data->courseid = $course->id;
638 $data->timeshift = DAYSECS;
639 $this->setUser($teacher);
640 $assign->reset_userdata($data);
641 $this->assertEquals(false, $assign->has_submissions_or_grades());
643 // Reload the instance data.
644 $instance = $DB->get_record('assign', array('id'=>$assign->get_instance()->id));
645 $this->assertEquals($now + DAYSECS, $instance->duedate);
647 // Test reset using assign_reset_userdata().
648 $assignduedate = $instance->duedate; // Keep old updated value for comparison.
649 $data->timeshift = (2 * DAYSECS);
650 assign_reset_userdata($data);
651 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
652 $this->assertEquals($assignduedate + (2 * DAYSECS), $instance->duedate);
654 // Create one more assignment and reset, make sure time shifted for previous assignment is not changed.
655 $assign2 = $this->create_instance($course, [
656 'assignsubmission_onlinetext_enabled' => 1,
657 'duedate' => $now,
659 $assignduedate = $instance->duedate;
660 $data->timeshift = 3*DAYSECS;
661 $assign2->reset_userdata($data);
662 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
663 $this->assertEquals($assignduedate, $instance->duedate);
664 $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
665 $this->assertEquals($now + 3*DAYSECS, $instance2->duedate);
667 // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date.
668 $assignduedate = $instance->duedate;
669 $assign2duedate = $instance2->duedate;
670 $data->timeshift = (4 * DAYSECS);
671 assign_reset_userdata($data);
672 $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
673 $this->assertEquals($assignduedate + (4 * DAYSECS), $instance->duedate);
674 $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
675 $this->assertEquals($assign2duedate + (4 * DAYSECS), $instance2->duedate);
678 public function test_plugin_settings() {
679 global $DB;
681 $this->resetAfterTest();
683 $course = $this->getDataGenerator()->create_course();
684 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
686 $now = time();
687 $this->setUser($teacher);
688 $assign = $this->create_instance($course, [
689 'assignsubmission_file_enabled' => 1,
690 'assignsubmission_file_maxfiles' => 12,
691 'assignsubmission_file_maxsizebytes' => 10,
694 $plugin = $assign->get_submission_plugin_by_type('file');
695 $this->assertEquals('12', $plugin->get_config('maxfilesubmissions'));
698 public function test_update_calendar() {
699 global $DB;
701 $this->resetAfterTest();
703 $course = $this->getDataGenerator()->create_course();
704 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
706 $this->setUser($teacher);
707 $userctx = context_user::instance($teacher->id)->id;
709 // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey.
710 $draftid = file_get_unused_draft_itemid();
711 $_REQUEST['introeditor'] = $draftid;
712 $_POST['introeditor'] = $draftid;
713 $_POST['sesskey'] = sesskey();
715 // Write links to a draft area.
716 $fakearealink1 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">link</a>', 'draftfile.php', $userctx,
717 'user', 'draft', $draftid);
718 $fakearealink2 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">new</a>', 'draftfile.php', $userctx,
719 'user', 'draft', $draftid);
721 // Create a new assignment with links to a draft area.
722 $now = time();
723 $assign = $this->create_instance($course, [
724 'duedate' => $now,
725 'intro' => $fakearealink1,
726 'introformat' => FORMAT_HTML
729 // See if there is an event in the calendar.
730 $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id);
731 $event = $DB->get_record('event', $params);
732 $this->assertNotEmpty($event);
733 $this->assertSame('link', $event->description); // The pluginfile links are removed.
735 // Make sure the same works when updating the assignment.
736 $instance = $assign->get_instance();
737 $instance->instance = $instance->id;
738 $instance->intro = $fakearealink2;
739 $instance->introformat = FORMAT_HTML;
740 $assign->update_instance($instance);
741 $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
742 $event = $DB->get_record('event', $params);
743 $this->assertNotEmpty($event);
744 $this->assertSame('new', $event->description); // The pluginfile links are removed.
746 // Create an assignment with a description that should be hidden.
747 $assign = $this->create_instance($course, [
748 'duedate' => $now + 160,
749 'alwaysshowdescription' => false,
750 'allowsubmissionsfromdate' => $now + 60,
751 'intro' => 'Some text',
754 // Get the event from the calendar.
755 $params = array('modulename'=>'assign', 'instance'=>$assign->get_instance()->id);
756 $event = $DB->get_record('event', [
757 'modulename' => 'assign',
758 'instance' => $assign->get_instance()->id,
761 $this->assertEmpty($event->description);
763 // Change the allowsubmissionfromdate to the past - do this directly in the DB
764 // because if we call the assignment update method - it will update the calendar
765 // and we want to test that this works from cron.
766 $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id'=>$assign->get_instance()->id));
767 // Run cron to update the event in the calendar.
768 assign::cron();
769 $event = $DB->get_record('event', $params);
771 $this->assertStringContainsString('Some text', $event->description);
775 public function test_update_instance() {
776 global $DB;
778 $this->resetAfterTest();
780 $course = $this->getDataGenerator()->create_course();
781 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
783 $this->setUser($teacher);
784 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
786 $now = time();
787 $instance = $assign->get_instance();
788 $instance->duedate = $now;
789 $instance->instance = $instance->id;
790 $instance->assignsubmission_onlinetext_enabled = 1;
792 $assign->update_instance($instance);
794 $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
795 $this->assertEquals($now, $instance->duedate);
798 public function test_cannot_submit_empty() {
799 global $PAGE;
801 $this->resetAfterTest();
803 $course = $this->getDataGenerator()->create_course();
804 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
806 $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
808 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
810 // Test you cannot see the submit button for an offline assignment regardless.
811 $this->setUser($student);
812 $output = $assign->view_student_summary($student, true);
813 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output, 'Can submit empty offline assignment');
816 public function test_cannot_submit_empty_no_submission() {
817 global $PAGE;
819 $this->resetAfterTest();
821 $course = $this->getDataGenerator()->create_course();
822 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
824 $assign = $this->create_instance($course, [
825 'submissiondrafts' => 1,
826 'assignsubmission_onlinetext_enabled' => 1,
829 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
831 // Test you cannot see the submit button for an online text assignment with no submission.
832 $this->setUser($student);
833 $output = $assign->view_student_summary($student, true);
834 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output, 'Cannot submit empty onlinetext assignment');
837 public function test_can_submit_with_submission() {
838 global $PAGE;
840 $this->resetAfterTest();
842 $course = $this->getDataGenerator()->create_course();
843 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
845 $assign = $this->create_instance($course, [
846 'submissiondrafts' => 1,
847 'assignsubmission_onlinetext_enabled' => 1,
850 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
852 // Add a draft.
853 $this->add_submission($student, $assign);
855 // Test you can see the submit button for an online text assignment with a submission.
856 $this->setUser($student);
857 $output = $assign->view_student_summary($student, true);
858 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output, 'Can submit non empty onlinetext assignment');
862 * Test new_submission_empty
864 * We only test combinations of plugins here. Individual plugins are tested
865 * in their respective test files.
867 * @dataProvider test_new_submission_empty_testcases
868 * @param string $data The file submission data
869 * @param bool $expected The expected return value
871 public function test_new_submission_empty($data, $expected) {
872 $this->resetAfterTest();
874 $course = $this->getDataGenerator()->create_course();
875 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
877 $assign = $this->create_instance($course, [
878 'assignsubmission_file_enabled' => 1,
879 'assignsubmission_file_maxfiles' => 12,
880 'assignsubmission_file_maxsizebytes' => 10,
881 'assignsubmission_onlinetext_enabled' => 1,
883 $this->setUser($student);
884 $submission = new stdClass();
886 if ($data['file'] && isset($data['file']['filename'])) {
887 $itemid = file_get_unused_draft_itemid();
888 $submission->files_filemanager = $itemid;
889 $data['file'] += ['contextid' => context_user::instance($student->id)->id, 'itemid' => $itemid];
890 $fs = get_file_storage();
891 $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']);
894 if ($data['onlinetext']) {
895 $submission->onlinetext_editor = ['text' => $data['onlinetext']];
898 $result = $assign->new_submission_empty($submission);
899 $this->assertTrue($result === $expected);
903 * Dataprovider for the test_new_submission_empty testcase
905 * @return array of testcases
907 public function test_new_submission_empty_testcases() {
908 return [
909 'With file and onlinetext' => [
911 'file' => [
912 'component' => 'user',
913 'filearea' => 'draft',
914 'filepath' => '/',
915 'filename' => 'not_a_virus.exe'
917 'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu'
919 false
924 public function test_list_participants() {
925 global $CFG;
927 $this->resetAfterTest();
929 $course = $this->getDataGenerator()->create_course();
930 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
932 // Create 10 students.
933 for ($i = 0; $i < 10; $i++) {
934 $this->getDataGenerator()->create_and_enrol($course, 'student');
937 $this->setUser($teacher);
938 $assign = $this->create_instance($course, ['grade' => 100]);
940 $this->assertCount(10, $assign->list_participants(null, true));
943 public function test_list_participants_activeenrol() {
944 global $CFG, $DB;
946 $this->resetAfterTest();
948 $course = $this->getDataGenerator()->create_course();
949 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
951 // Create 10 students.
952 for ($i = 0; $i < 10; $i++) {
953 $this->getDataGenerator()->create_and_enrol($course, 'student');
956 // Create 10 suspended students.
957 for ($i = 0; $i < 10; $i++) {
958 $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
961 $this->setUser($teacher);
962 set_user_preference('grade_report_showonlyactiveenrol', false);
963 $assign = $this->create_instance($course, ['grade' => 100]);
965 $this->assertCount(10, $assign->list_participants(null, true));
968 public function test_list_participants_with_group_restriction() {
969 global $CFG;
971 $this->resetAfterTest();
973 $course = $this->getDataGenerator()->create_course();
974 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
975 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
976 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
977 $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
979 // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group.
980 $CFG->enableavailability = true;
982 $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
983 $assign = $this->create_instance($course, [
984 'grade' => 100,
985 'availability' => json_encode(
986 \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)])
990 groups_add_member($specialgroup, $student);
991 groups_add_member($specialgroup, $otherstudent);
992 $this->assertEquals(2, count($assign->list_participants(null, true)));
995 public function test_get_participant_user_not_exist() {
996 $this->resetAfterTest();
997 $course = $this->getDataGenerator()->create_course();
999 $assign = $this->create_instance($course);
1000 $this->assertNull($assign->get_participant('-1'));
1003 public function test_get_participant_not_enrolled() {
1004 $this->resetAfterTest();
1005 $course = $this->getDataGenerator()->create_course();
1006 $assign = $this->create_instance($course);
1008 $user = $this->getDataGenerator()->create_user();
1009 $this->assertNull($assign->get_participant($user->id));
1012 public function test_get_participant_no_submission() {
1013 $this->resetAfterTest();
1014 $course = $this->getDataGenerator()->create_course();
1015 $assign = $this->create_instance($course);
1016 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1018 $participant = $assign->get_participant($student->id);
1020 $this->assertEquals($student->id, $participant->id);
1021 $this->assertFalse($participant->submitted);
1022 $this->assertFalse($participant->requiregrading);
1023 $this->assertFalse($participant->grantedextension);
1026 public function test_get_participant_granted_extension() {
1027 $this->resetAfterTest();
1028 $course = $this->getDataGenerator()->create_course();
1029 $assign = $this->create_instance($course);
1030 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1031 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1033 $this->setUser($teacher);
1034 $assign->save_user_extension($student->id, time());
1035 $participant = $assign->get_participant($student->id);
1037 $this->assertEquals($student->id, $participant->id);
1038 $this->assertFalse($participant->submitted);
1039 $this->assertFalse($participant->requiregrading);
1040 $this->assertTrue($participant->grantedextension);
1043 public function test_get_participant_with_ungraded_submission() {
1044 $this->resetAfterTest();
1045 $course = $this->getDataGenerator()->create_course();
1046 $assign = $this->create_instance($course);
1047 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1048 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1050 // Simulate a submission.
1051 $this->add_submission($student, $assign);
1052 $this->submit_for_grading($student, $assign);
1054 $participant = $assign->get_participant($student->id);
1056 $this->assertEquals($student->id, $participant->id);
1057 $this->assertTrue($participant->submitted);
1058 $this->assertTrue($participant->requiregrading);
1059 $this->assertFalse($participant->grantedextension);
1062 public function test_get_participant_with_graded_submission() {
1063 $this->resetAfterTest();
1064 $course = $this->getDataGenerator()->create_course();
1065 $assign = $this->create_instance($course);
1066 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1067 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1069 // Simulate a submission.
1070 $this->add_submission($student, $assign);
1071 $this->submit_for_grading($student, $assign);
1073 $this->mark_submission($teacher, $assign, $student, 50.0);
1075 $data = new stdClass();
1076 $data->grade = '50.0';
1077 $assign->testable_apply_grade_to_user($data, $student->id, 0);
1079 $participant = $assign->get_participant($student->id);
1081 $this->assertEquals($student->id, $participant->id);
1082 $this->assertTrue($participant->submitted);
1083 $this->assertFalse($participant->requiregrading);
1084 $this->assertFalse($participant->grantedextension);
1088 * No active group and non-group submissions disallowed => 2 groups.
1090 public function test_count_teams_no_active_non_group_allowed() {
1091 $this->resetAfterTest();
1093 $course = $this->getDataGenerator()->create_course();
1094 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1095 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1096 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1098 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1099 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1100 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1101 groups_add_member($group1, $student1);
1102 groups_add_member($group2, $student2);
1104 $this->setUser($teacher);
1105 $assign = $this->create_instance($course, ['teamsubmission' => 1]);
1107 $this->assertEquals(2, $assign->count_teams());
1111 * No active group and non group submissions allowed => 2 groups + the default one.
1113 public function test_count_teams_non_group_allowed() {
1114 $this->resetAfterTest();
1116 $course = $this->getDataGenerator()->create_course();
1117 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1118 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1119 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1120 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1122 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1123 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1125 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1126 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1127 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1129 groups_add_member($group1, $student1);
1130 groups_add_member($group2, $student2);
1132 $assign = $this->create_instance($course, [
1133 'teamsubmission' => 1,
1134 'teamsubmissiongroupingid' => $grouping->id,
1135 'preventsubmissionnotingroup' => false,
1138 $this->setUser($teacher);
1139 $this->assertEquals(3, $assign->count_teams());
1141 // Active group only.
1142 $this->assertEquals(1, $assign->count_teams($group1->id));
1143 $this->assertEquals(1, $assign->count_teams($group2->id));
1147 * Active group => just selected one.
1149 public function test_count_teams_no_active_group() {
1150 $this->resetAfterTest();
1152 $course = $this->getDataGenerator()->create_course();
1153 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1154 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1155 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1156 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1158 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1159 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1161 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1162 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1163 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1165 groups_add_member($group1, $student1);
1166 groups_add_member($group2, $student2);
1168 $assign = $this->create_instance($course, [
1169 'teamsubmission' => 1,
1170 'preventsubmissionnotingroup' => true,
1173 $this->setUser($teacher);
1174 $this->assertEquals(2, $assign->count_teams());
1176 // Active group only.
1177 $this->assertEquals(1, $assign->count_teams($group1->id));
1178 $this->assertEquals(1, $assign->count_teams($group2->id));
1182 * Active group => just selected one.
1184 public function test_count_teams_groups_only() {
1185 $this->resetAfterTest();
1187 $course = $this->getDataGenerator()->create_course();
1188 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1190 $assign = $this->create_instance($course, [
1191 'teamsubmission' => 1,
1192 'teamsubmissiongroupingid' => $grouping->id,
1193 'preventsubmissionnotingroup' => false,
1195 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1197 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1198 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1199 groups_add_member($group1, $student1);
1201 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1202 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1203 groups_add_member($group2, $student2);
1205 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1206 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1208 $this->setUser($teacher);
1210 $assign = $this->create_instance($course, [
1211 'teamsubmission' => 1,
1212 'preventsubmissionnotingroup' => true,
1214 $this->assertEquals(2, $assign->count_teams());
1217 public function test_submit_to_default_group() {
1218 global $DB, $SESSION;
1220 $this->resetAfterTest();
1222 $course = $this->getDataGenerator()->create_course();
1223 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1224 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1226 $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1227 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1229 $assign = $this->create_instance($course, [
1230 'teamsubmission' => 1,
1231 'assignsubmission_onlinetext_enabled' => 1,
1232 'submissiondrafts' => 0,
1233 'groupmode' => VISIBLEGROUPS,
1236 $usergroup = $assign->get_submission_group($student->id);
1237 $this->assertFalse($usergroup, 'New student is in default group');
1239 // Add a submission.
1240 $this->add_submission($student, $assign);
1241 $this->submit_for_grading($student, $assign);
1243 // Set active groups to all groups.
1244 $this->setUser($teacher);
1245 $SESSION->activegroup[$course->id]['aag'][0] = 0;
1246 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1248 // Set an active group.
1249 $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id;
1250 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1253 public function test_count_submissions_no_draft() {
1254 $this->resetAfterTest();
1256 $course = $this->getDataGenerator()->create_course();
1257 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1258 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1260 $assign = $this->create_instance($course, [
1261 'assignsubmission_onlinetext_enabled' => 1,
1264 $assign->get_user_submission($student->id, true);
1266 // Note: Drafts count as a submission.
1267 $this->assertEquals(0, $assign->count_grades());
1268 $this->assertEquals(0, $assign->count_submissions());
1269 $this->assertEquals(1, $assign->count_submissions(true));
1270 $this->assertEquals(0, $assign->count_submissions_need_grading());
1271 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1272 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1273 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1274 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1277 public function test_count_submissions_draft() {
1278 $this->resetAfterTest();
1280 $course = $this->getDataGenerator()->create_course();
1281 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1282 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1284 $assign = $this->create_instance($course, [
1285 'assignsubmission_onlinetext_enabled' => 1,
1288 $this->add_submission($student, $assign);
1290 // Note: Drafts count as a submission.
1291 $this->assertEquals(0, $assign->count_grades());
1292 $this->assertEquals(1, $assign->count_submissions());
1293 $this->assertEquals(1, $assign->count_submissions(true));
1294 $this->assertEquals(0, $assign->count_submissions_need_grading());
1295 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1296 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1297 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1298 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1301 public function test_count_submissions_submitted() {
1302 global $SESSION;
1304 $this->resetAfterTest();
1306 $course = $this->getDataGenerator()->create_course();
1307 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1308 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1310 $assign = $this->create_instance($course, [
1311 'assignsubmission_onlinetext_enabled' => 1,
1314 $this->add_submission($student, $assign);
1315 $this->submit_for_grading($student, $assign);
1317 $this->assertEquals(0, $assign->count_grades());
1318 $this->assertEquals(1, $assign->count_submissions());
1319 $this->assertEquals(1, $assign->count_submissions(true));
1320 $this->assertEquals(1, $assign->count_submissions_need_grading());
1321 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1322 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1323 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1324 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1327 public function test_count_submissions_graded() {
1328 $this->resetAfterTest();
1330 $course = $this->getDataGenerator()->create_course();
1331 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1332 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1334 $assign = $this->create_instance($course, [
1335 'assignsubmission_onlinetext_enabled' => 1,
1338 $this->add_submission($student, $assign);
1339 $this->submit_for_grading($student, $assign);
1340 $this->mark_submission($teacher, $assign, $student, 50.0);
1342 // Although it has been graded, it is still marked as submitted.
1343 $this->assertEquals(1, $assign->count_grades());
1344 $this->assertEquals(1, $assign->count_submissions());
1345 $this->assertEquals(1, $assign->count_submissions(true));
1346 $this->assertEquals(0, $assign->count_submissions_need_grading());
1347 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1348 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1349 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1350 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1353 public function test_count_submissions_graded_group() {
1354 global $SESSION;
1356 $this->resetAfterTest();
1358 $course = $this->getDataGenerator()->create_course();
1359 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1360 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1361 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1362 $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1363 groups_add_member($group, $student);
1365 $assign = $this->create_instance($course, [
1366 'assignsubmission_onlinetext_enabled' => 1,
1367 'groupmode' => VISIBLEGROUPS,
1370 $this->add_submission($student, $assign);
1371 $this->submit_for_grading($student, $assign);
1373 // The user should still be listed when fetching all groups.
1374 $this->setUser($teacher);
1375 $SESSION->activegroup[$course->id]['aag'][0] = 0;
1376 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1378 // The user should still be listed when fetching just their group.
1379 $SESSION->activegroup[$course->id]['aag'][0] = $group->id;
1380 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1382 // The user should still be listed when fetching just their group.
1383 $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id;
1384 $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1387 // TODO
1388 public function x_test_count_submissions_for_team() {
1389 $this->resetAfterTest();
1391 $course = $this->getDataGenerator()->create_course();
1392 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1393 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1394 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1395 $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1396 groups_add_member($group, $student);
1398 $assign = $this->create_instance($course, [
1399 'assignsubmission_onlinetext_enabled' => 1,
1400 'teamsubmission' => 1,
1403 // Add a graded submission.
1404 $this->add_submission($student, $assign);
1408 // Simulate adding a grade.
1409 $this->setUser($teacher);
1410 $data = new stdClass();
1411 $data->grade = '50.0';
1412 $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
1414 // Simulate a submission.
1415 $this->setUser($this->extrastudents[1]);
1416 $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
1417 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1418 $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1419 $data = new stdClass();
1420 $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1421 'text' => 'Submission text',
1422 'format' => FORMAT_MOODLE);
1423 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1424 $plugin->save($submission, $data);
1426 // Simulate a submission.
1427 $this->setUser($this->extrastudents[2]);
1428 $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
1429 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1430 $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1431 $data = new stdClass();
1432 $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1433 'text' => 'Submission text',
1434 'format' => FORMAT_MOODLE);
1435 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1436 $plugin->save($submission, $data);
1438 // Simulate a submission.
1439 $this->setUser($this->extrastudents[3]);
1440 $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
1441 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1442 $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1443 $data = new stdClass();
1444 $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
1445 'text' => 'Submission text',
1446 'format' => FORMAT_MOODLE);
1447 $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1448 $plugin->save($submission, $data);
1450 // Simulate adding a grade.
1451 $this->setUser($teacher);
1452 $data = new stdClass();
1453 $data->grade = '50.0';
1454 $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1455 $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1457 // Create a new submission with status NEW.
1458 $this->setUser($this->extrastudents[4]);
1459 $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
1461 $this->assertEquals(2, $assign->count_grades());
1462 $this->assertEquals(4, $assign->count_submissions());
1463 $this->assertEquals(5, $assign->count_submissions(true));
1464 $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1465 $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1468 public function test_get_grading_userid_list_only_active() {
1469 $this->resetAfterTest();
1471 $course = $this->getDataGenerator()->create_course();
1472 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1473 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1474 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1476 $this->setUser($teacher);
1478 $assign = $this->create_instance($course);
1479 $this->assertCount(1, $assign->testable_get_grading_userid_list());
1482 public function test_get_grading_userid_list_all() {
1483 $this->resetAfterTest();
1485 $course = $this->getDataGenerator()->create_course();
1486 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1487 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1488 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1490 $this->setUser($teacher);
1491 set_user_preference('grade_report_showonlyactiveenrol', false);
1493 $assign = $this->create_instance($course);
1494 $this->assertCount(2, $assign->testable_get_grading_userid_list());
1497 public function test_cron() {
1498 global $PAGE;
1499 $this->resetAfterTest();
1501 // First run cron so there are no messages waiting to be sent (from other tests).
1502 cron_setup_user();
1503 assign::cron();
1505 $course = $this->getDataGenerator()->create_course();
1506 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1507 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1509 // Now create an assignment and add some feedback.
1510 $this->setUser($teacher);
1511 $assign = $this->create_instance($course, [
1512 'sendstudentnotifications' => 1,
1515 $this->add_submission($student, $assign);
1516 $this->submit_for_grading($student, $assign);
1517 $this->mark_submission($teacher, $assign, $student, 50.0);
1519 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1520 cron_setup_user();
1521 $sink = $this->redirectMessages();
1522 assign::cron();
1523 $messages = $sink->get_messages();
1525 $this->assertEquals(1, count($messages));
1526 $this->assertEquals(1, $messages[0]->notification);
1527 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1528 // Test customdata.
1529 $customdata = json_decode($messages[0]->customdata);
1530 $this->assertEquals($assign->get_course_module()->id, $customdata->cmid);
1531 $this->assertEquals($assign->get_instance()->id, $customdata->instance);
1532 $this->assertEquals('feedbackavailable', $customdata->messagetype);
1533 $userpicture = new user_picture($teacher);
1534 $userpicture->size = 1; // Use f1 size.
1535 $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
1536 $this->assertEquals(0, $customdata->uniqueidforuser); // Not used in this case.
1537 $this->assertFalse($customdata->blindmarking);
1540 public function test_cron_without_notifications() {
1541 $this->resetAfterTest();
1543 // First run cron so there are no messages waiting to be sent (from other tests).
1544 cron_setup_user();
1545 assign::cron();
1547 $course = $this->getDataGenerator()->create_course();
1548 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1549 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1551 // Now create an assignment and add some feedback.
1552 $this->setUser($teacher);
1553 $assign = $this->create_instance($course, [
1554 'sendstudentnotifications' => 1,
1557 $this->add_submission($student, $assign);
1558 $this->submit_for_grading($student, $assign);
1559 $this->mark_submission($teacher, $assign, $student, 50.0, [
1560 'sendstudentnotifications' => 0,
1563 cron_setup_user();
1564 $sink = $this->redirectMessages();
1565 assign::cron();
1566 $messages = $sink->get_messages();
1568 $this->assertEquals(0, count($messages));
1571 public function test_cron_regraded() {
1572 $this->resetAfterTest();
1574 // First run cron so there are no messages waiting to be sent (from other tests).
1575 cron_setup_user();
1576 assign::cron();
1578 $course = $this->getDataGenerator()->create_course();
1579 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1580 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1582 // Now create an assignment and add some feedback.
1583 $this->setUser($teacher);
1584 $assign = $this->create_instance($course, [
1585 'sendstudentnotifications' => 1,
1588 $this->add_submission($student, $assign);
1589 $this->submit_for_grading($student, $assign);
1590 $this->mark_submission($teacher, $assign, $student, 50.0);
1592 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1593 cron_setup_user();
1594 assign::cron();
1596 // Regrade.
1597 $this->mark_submission($teacher, $assign, $student, 50.0);
1599 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1600 cron_setup_user();
1601 $sink = $this->redirectMessages();
1602 assign::cron();
1603 $messages = $sink->get_messages();
1605 $this->assertEquals(1, count($messages));
1606 $this->assertEquals(1, $messages[0]->notification);
1607 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1611 * Test delivery of grade notifications as controlled by marking workflow.
1613 public function test_markingworkflow_cron() {
1614 $this->resetAfterTest();
1616 // First run cron so there are no messages waiting to be sent (from other tests).
1617 cron_setup_user();
1618 assign::cron();
1620 $course = $this->getDataGenerator()->create_course();
1621 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1622 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1624 // Now create an assignment and add some feedback.
1625 $this->setUser($teacher);
1626 $assign = $this->create_instance($course, [
1627 'sendstudentnotifications' => 1,
1628 'markingworkflow' => 1,
1631 // Mark a submission but set the workflowstate to an unreleased state.
1632 // This should not trigger a notification.
1633 $this->add_submission($student, $assign);
1634 $this->submit_for_grading($student, $assign);
1635 $this->mark_submission($teacher, $assign, $student, 50.0, [
1636 'sendstudentnotifications' => 1,
1637 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1640 cron_setup_user();
1641 $sink = $this->redirectMessages();
1642 assign::cron();
1643 $messages = $sink->get_messages();
1645 $this->assertEquals(0, count($messages));
1647 // Transition to the released state.
1648 $this->setUser($teacher);
1649 $submission = $assign->get_user_submission($student->id, true);
1650 $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1651 $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1653 // Now run cron and see that one message was sent.
1654 cron_setup_user();
1655 $sink = $this->redirectMessages();
1656 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1657 assign::cron();
1658 $messages = $sink->get_messages();
1660 $this->assertEquals(1, count($messages));
1661 $this->assertEquals(1, $messages[0]->notification);
1662 $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1665 public function test_cron_message_includes_courseid() {
1666 $this->resetAfterTest();
1668 // First run cron so there are no messages waiting to be sent (from other tests).
1669 cron_setup_user();
1670 assign::cron();
1672 $course = $this->getDataGenerator()->create_course();
1673 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1674 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1676 // Now create an assignment and add some feedback.
1677 $this->setUser($teacher);
1678 $assign = $this->create_instance($course, [
1679 'sendstudentnotifications' => 1,
1682 // Mark a submission but set the workflowstate to an unreleased state.
1683 // This should not trigger a notification.
1684 $this->add_submission($student, $assign);
1685 $this->submit_for_grading($student, $assign);
1686 $this->mark_submission($teacher, $assign, $student);
1687 phpunit_util::stop_message_redirection();
1689 // Now run cron and see that one message was sent.
1690 cron_setup_user();
1691 $this->preventResetByRollback();
1692 $sink = $this->redirectEvents();
1693 $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1694 assign::cron();
1696 $events = $sink->get_events();
1697 $event = reset($events);
1698 $this->assertInstanceOf('\core\event\notification_sent', $event);
1699 $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1700 $sink->close();
1703 public function test_is_graded() {
1704 $this->resetAfterTest();
1706 $course = $this->getDataGenerator()->create_course();
1707 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1708 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1709 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1711 $assign = $this->create_instance($course);
1713 $this->add_submission($student, $assign);
1714 $this->submit_for_grading($student, $assign);
1715 $this->mark_submission($teacher, $assign, $student, 50.0);
1717 $this->setUser($teacher);
1718 $this->assertEquals(true, $assign->testable_is_graded($student->id));
1719 $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1722 public function test_can_grade() {
1723 global $DB;
1725 $this->resetAfterTest();
1727 $course = $this->getDataGenerator()->create_course();
1728 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1729 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1731 $assign = $this->create_instance($course);
1733 $this->setUser($student);
1734 $this->assertEquals(false, $assign->can_grade());
1736 $this->setUser($teacher);
1737 $this->assertEquals(true, $assign->can_grade());
1739 // Test the viewgrades capability for other users.
1740 $this->setUser();
1741 $this->assertTrue($assign->can_grade($teacher->id));
1742 $this->assertFalse($assign->can_grade($student->id));
1744 // Test the viewgrades capability - without mod/assign:grade.
1745 $this->setUser($student);
1747 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1748 assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1749 $this->assertEquals(false, $assign->can_grade());
1752 public function test_can_view_submission() {
1753 global $DB;
1755 $this->resetAfterTest();
1757 $course = $this->getDataGenerator()->create_course();
1758 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1759 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1760 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1761 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1762 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1764 $assign = $this->create_instance($course);
1766 $this->setUser($student);
1767 $this->assertEquals(true, $assign->can_view_submission($student->id));
1768 $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1769 $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1771 $this->setUser($teacher);
1772 $this->assertEquals(true, $assign->can_view_submission($student->id));
1773 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1774 $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1775 $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1777 $this->setUser($editingteacher);
1778 $this->assertEquals(true, $assign->can_view_submission($student->id));
1779 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1780 $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1781 $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1783 // Test the viewgrades capability - without mod/assign:grade.
1784 $this->setUser($student);
1785 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1786 assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1787 $this->assertEquals(true, $assign->can_view_submission($student->id));
1788 $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1789 $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1790 $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1793 public function test_update_submission() {
1794 $this->resetAfterTest();
1796 $course = $this->getDataGenerator()->create_course();
1797 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1798 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1800 $assign = $this->create_instance($course);
1802 $this->add_submission($student, $assign);
1803 $submission = $assign->get_user_submission($student->id, 0);
1804 $assign->testable_update_submission($submission, $student->id, true, true);
1806 $this->setUser($teacher);
1808 // Verify the gradebook update.
1809 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1811 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1812 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1815 public function test_update_submission_team() {
1816 $this->resetAfterTest();
1818 $course = $this->getDataGenerator()->create_course();
1819 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1820 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1822 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1823 groups_add_member($group, $student);
1825 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1826 groups_add_member($group, $otherstudent);
1828 $assign = $this->create_instance($course, [
1829 'teamsubmission' => 1,
1832 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1833 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1834 $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1836 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1837 $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1838 $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1840 $this->add_submission($student, $assign);
1841 $submission = $assign->get_group_submission($student->id, 0, true);
1842 $assign->testable_update_submission($submission, $student->id, true, true);
1844 // Verify the gradebook update for the student.
1845 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1847 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1848 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1850 // Verify the gradebook update for the other student.
1851 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1853 $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1854 $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1857 public function test_update_submission_suspended() {
1858 $this->resetAfterTest();
1860 $course = $this->getDataGenerator()->create_course();
1861 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1862 $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1864 $assign = $this->create_instance($course);
1866 $this->add_submission($student, $assign);
1867 $submission = $assign->get_user_submission($student->id, 0);
1868 $assign->testable_update_submission($submission, $student->id, true, false);
1870 $this->setUser($teacher);
1872 // Verify the gradebook update.
1873 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1875 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1876 $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1879 public function test_update_submission_blind() {
1880 $this->resetAfterTest();
1882 $course = $this->getDataGenerator()->create_course();
1883 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1884 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1886 $assign = $this->create_instance($course, [
1887 'blindmarking' => 1,
1890 $this->add_submission($student, $assign);
1891 $submission = $assign->get_user_submission($student->id, 0);
1892 $assign->testable_update_submission($submission, $student->id, true, false);
1894 // Verify the gradebook update.
1895 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1897 // The usermodified is not set because this is blind marked.
1898 $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1899 $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1902 public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
1903 global $PAGE;
1905 $this->resetAfterTest();
1907 $course = $this->getDataGenerator()->create_course();
1908 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1909 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1911 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1912 groups_add_member($group, $student);
1914 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1915 groups_add_member($group, $otherstudent);
1917 $assign = $this->create_instance($course, [
1918 'teamsubmission' => 1,
1919 'assignsubmission_onlinetext_enabled' => 1,
1920 'submissiondrafts' => 1,
1921 'requireallteammemberssubmit' => 1,
1924 // Now verify group assignments.
1925 $this->setUser($teacher);
1926 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1928 // Add a submission.
1929 $this->add_submission($student, $assign);
1931 // Check we can see the submit button.
1932 $this->setUser($student);
1933 $output = $assign->view_student_summary($student, true);
1934 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1936 $submission = $assign->get_group_submission($student->id, 0, true);
1937 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1938 $assign->testable_update_submission($submission, $student->id, true, true);
1940 // Check that the student does not see "Submit" button.
1941 $output = $assign->view_student_summary($student, true);
1942 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1944 // Change to another user in the same group.
1945 $this->setUser($otherstudent);
1946 $output = $assign->view_student_summary($otherstudent, true);
1947 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1949 $submission = $assign->get_group_submission($otherstudent->id, 0, true);
1950 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1951 $assign->testable_update_submission($submission, $otherstudent->id, true, true);
1952 $output = $assign->view_student_summary($otherstudent, true);
1953 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
1956 public function test_group_submissions_submit_for_marking() {
1957 global $PAGE;
1959 $this->resetAfterTest();
1961 $course = $this->getDataGenerator()->create_course();
1962 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1963 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1965 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1966 groups_add_member($group, $student);
1968 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1969 groups_add_member($group, $otherstudent);
1971 // Now verify group assignments.
1972 $this->setUser($teacher);
1973 $time = time();
1974 $assign = $this->create_instance($course, [
1975 'teamsubmission' => 1,
1976 'assignsubmission_onlinetext_enabled' => 1,
1977 'submissiondrafts' => 1,
1978 'requireallteammemberssubmit' => 0,
1979 'duedate' => $time - (2 * DAYSECS),
1981 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1983 // Add a submission.
1984 $this->add_submission($student, $assign);
1987 // Check we can see the submit button.
1988 $output = $assign->view_student_summary($student, true);
1989 $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
1990 $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
1991 $difftime = time() - $time;
1992 $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
1994 $submission = $assign->get_group_submission($student->id, 0, true);
1995 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1996 $assign->testable_update_submission($submission, $student->id, true, true);
1998 // Check that the student does not see "Submit" button.
1999 $output = $assign->view_student_summary($student, true);
2000 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2002 // Change to another user in the same group.
2003 $this->setUser($otherstudent);
2004 $output = $assign->view_student_summary($otherstudent, true);
2005 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2007 // Check that time remaining is not overdue.
2008 $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2009 $difftime = time() - $time;
2010 $this->assertStringContainsString(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2012 $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2013 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2014 $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2015 $output = $assign->view_student_summary($otherstudent, true);
2016 $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2019 public function test_submissions_open() {
2020 global $DB;
2022 $this->resetAfterTest();
2024 $course = $this->getDataGenerator()->create_course();
2025 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2026 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2027 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2028 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2029 $suspendedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
2031 $this->setAdminUser();
2033 $now = time();
2034 $tomorrow = $now + DAYSECS;
2035 $oneweek = $now + WEEKSECS;
2036 $yesterday = $now - DAYSECS;
2038 $assign = $this->create_instance($course);
2039 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2041 $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
2042 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2044 $assign = $this->create_instance($course, ['duedate' => $yesterday]);
2045 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2047 $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
2048 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2050 $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2051 $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2053 $assign->testable_save_user_extension($student->id, $tomorrow);
2054 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2056 $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2057 $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2059 $this->setUser($student);
2060 $submission = $assign->get_user_submission($student->id, true);
2061 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2062 $assign->testable_update_submission($submission, $student->id, true, false);
2064 $this->setUser($teacher);
2065 $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2068 public function test_get_graders() {
2069 global $DB;
2071 $this->resetAfterTest();
2073 $course = $this->getDataGenerator()->create_course();
2074 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2075 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2076 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2078 $this->setAdminUser();
2080 // Create an assignment with no groups.
2081 $assign = $this->create_instance($course);
2082 $this->assertCount(2, $assign->testable_get_graders($student->id));
2085 public function test_get_graders_separate_groups() {
2086 global $DB;
2088 $this->resetAfterTest();
2090 $course = $this->getDataGenerator()->create_course();
2091 $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2092 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2093 $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2094 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2095 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2096 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2098 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2099 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2100 groups_add_member($group1, $student);
2102 $this->setAdminUser();
2104 // Force create an assignment with SEPARATEGROUPS.
2105 $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2106 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2108 $assign = $this->create_instance($course, [
2109 'groupingid' => $grouping->id,
2110 'groupmode' => SEPARATEGROUPS,
2113 $this->assertCount(4, $assign->testable_get_graders($student->id));
2115 // Note the second student is in a group that is not in the grouping.
2116 // This means that we get all graders that are not in a group in the grouping.
2117 $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2120 public function test_get_notified_users() {
2121 global $CFG, $DB;
2123 $this->resetAfterTest();
2125 $course = $this->getDataGenerator()->create_course();
2126 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2127 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2128 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2130 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2131 groups_add_member($group1, $teacher);
2133 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2134 groups_add_member($group1, $editingteacher);
2136 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2137 groups_add_member($group1, $student);
2139 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2140 $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2142 $capability = 'mod/assign:receivegradernotifications';
2143 $coursecontext = context_course::instance($course->id);
2144 $role = $DB->get_record('role', array('shortname' => 'teacher'));
2146 $this->setUser($teacher);
2148 // Create an assignment with no groups.
2149 $assign = $this->create_instance($course);
2151 $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2153 // Change nonediting teachers role to not receive grader notifications.
2154 assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2156 // Only the editing teachers will be returned.
2157 $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2159 // Note the second student is in a group that is not in the grouping.
2160 // This means that we get all graders that are not in a group in the grouping.
2161 $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2164 public function test_get_notified_users_in_grouping() {
2165 global $CFG, $DB;
2167 $this->resetAfterTest();
2169 $course = $this->getDataGenerator()->create_course();
2170 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2171 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2172 $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2174 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2175 groups_add_member($group1, $teacher);
2177 $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2178 groups_add_member($group1, $editingteacher);
2180 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2181 groups_add_member($group1, $student);
2183 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2184 $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2186 // Force create an assignment with SEPARATEGROUPS.
2187 $assign = $this->create_instance($course, [
2188 'groupingid' => $grouping->id,
2189 'groupmode' => SEPARATEGROUPS,
2192 // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2193 $this->setUser($student);
2194 $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2196 // Note the second student is in a group that is not in the grouping.
2197 // This means that we get all graders that are not in a group in the grouping.
2198 $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2200 // Change nonediting teachers role to not receive grader notifications.
2201 $capability = 'mod/assign:receivegradernotifications';
2202 $coursecontext = context_course::instance($course->id);
2203 $role = $DB->get_record('role', ['shortname' => 'teacher']);
2204 assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2206 // Only the editing teachers will be returned.
2207 $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2209 // Note the second student is in a group that is not in the grouping.
2210 // This means that we get all graders that are not in a group in the grouping.
2211 // Unfortunately there are no editing teachers who are not in a group.
2212 $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2215 public function test_group_members_only() {
2216 global $CFG;
2218 $this->resetAfterTest();
2220 $course = $this->getDataGenerator()->create_course();
2221 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2222 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2223 $this->getDataGenerator()->create_grouping_group([
2224 'groupid' => $group1->id,
2225 'groupingid' => $grouping->id,
2228 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2229 $this->getDataGenerator()->create_grouping_group([
2230 'groupid' => $group2->id,
2231 'groupingid' => $grouping->id,
2234 $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2236 // Add users in the following groups
2237 // - Teacher - Group 1.
2238 // - Student - Group 1.
2239 // - Student - Group 2.
2240 // - Student - Unrelated Group
2241 // - Student - No group.
2242 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2243 groups_add_member($group1, $teacher);
2245 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2246 groups_add_member($group1, $student);
2248 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2249 groups_add_member($group2, $otherstudent);
2251 $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2252 groups_add_member($group2, $otherstudent);
2254 $this->getDataGenerator()->create_and_enrol($course, 'student');
2256 $this->setAdminUser();
2258 $CFG->enableavailability = true;
2259 $assign = $this->create_instance($course, [], [
2260 'availability' => json_encode(
2261 \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2263 'groupingid' => $grouping->id,
2266 // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2267 // group, or the student in an unrelated group.
2268 $this->setUser($teacher);
2269 $participants = $assign->list_participants(0, true);
2270 $this->assertCount(2, $participants);
2271 $this->assertTrue(isset($participants[$student->id]));
2272 $this->assertTrue(isset($participants[$otherstudent->id]));
2275 public function test_get_uniqueid_for_user() {
2276 $this->resetAfterTest();
2278 $course = $this->getDataGenerator()->create_course();
2279 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2280 $students = [];
2281 for ($i = 0; $i < 10; $i++) {
2282 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2283 $students[$student->id] = $student;
2286 $this->setUser($teacher);
2287 $assign = $this->create_instance($course);
2289 foreach ($students as $student) {
2290 $uniqueid = $assign->get_uniqueid_for_user($student->id);
2291 $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2295 public function test_show_student_summary() {
2296 global $CFG, $PAGE;
2298 $this->resetAfterTest();
2300 $course = $this->getDataGenerator()->create_course();
2301 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2302 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2303 $this->setUser($teacher);
2304 $assign = $this->create_instance($course);
2305 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2307 // No feedback should be available because this student has not been graded.
2308 $this->setUser($student);
2309 $output = $assign->view_student_summary($student, true);
2310 $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if there is no grade');
2312 // Simulate adding a grade.
2313 $this->add_submission($student, $assign);
2314 $this->submit_for_grading($student, $assign);
2315 $this->mark_submission($teacher, $assign, $student);
2317 // Now we should see the feedback.
2318 $this->setUser($student);
2319 $output = $assign->view_student_summary($student, true);
2320 $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
2322 // Now hide the grade in gradebook.
2323 $this->setUser($teacher);
2324 require_once($CFG->libdir.'/gradelib.php');
2325 $gradeitem = new grade_item(array(
2326 'itemtype' => 'mod',
2327 'itemmodule' => 'assign',
2328 'iteminstance' => $assign->get_instance()->id,
2329 'courseid' => $course->id));
2331 $gradeitem->set_hidden(1, false);
2333 // No feedback should be available because the grade is hidden.
2334 $this->setUser($student);
2335 $output = $assign->view_student_summary($student, true);
2336 $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2339 public function test_show_student_summary_with_feedback() {
2340 global $CFG, $PAGE;
2342 $this->resetAfterTest();
2344 $course = $this->getDataGenerator()->create_course();
2345 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2346 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2347 $this->setUser($teacher);
2348 $assign = $this->create_instance($course, [
2349 'assignfeedback_comments_enabled' => 1
2351 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2353 // No feedback should be available because this student has not been graded.
2354 $this->setUser($student);
2355 $output = $assign->view_student_summary($student, true);
2356 $this->assertDoesNotMatchRegularExpression('/Feedback/', $output);
2358 // Simulate adding a grade.
2359 $this->add_submission($student, $assign);
2360 $this->submit_for_grading($student, $assign);
2361 $this->mark_submission($teacher, $assign, $student, null, [
2362 'assignfeedbackcomments_editor' => [
2363 'text' => 'Tomato sauce',
2364 'format' => FORMAT_MOODLE,
2368 // Should have feedback but no grade.
2369 $this->setUser($student);
2370 $output = $assign->view_student_summary($student, true);
2371 $this->assertMatchesRegularExpression('/Feedback/', $output);
2372 $this->assertMatchesRegularExpression('/Tomato sauce/', $output);
2373 $this->assertDoesNotMatchRegularExpression('/Grade/', $output, 'Do not show grade when there is no grade.');
2374 $this->assertDoesNotMatchRegularExpression('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2376 // Add a grade now.
2377 $this->mark_submission($teacher, $assign, $student, 50.0, [
2378 'assignfeedbackcomments_editor' => [
2379 'text' => 'Bechamel sauce',
2380 'format' => FORMAT_MOODLE,
2384 // Should have feedback but no grade.
2385 $this->setUser($student);
2386 $output = $assign->view_student_summary($student, true);
2387 $this->assertDoesNotMatchRegularExpression('/Tomato sauce/', $output);
2388 $this->assertMatchesRegularExpression('/Bechamel sauce/', $output);
2389 $this->assertMatchesRegularExpression('/Grade/', $output);
2390 $this->assertMatchesRegularExpression('/Graded on/', $output);
2392 // Now hide the grade in gradebook.
2393 $this->setUser($teacher);
2394 $gradeitem = new grade_item(array(
2395 'itemtype' => 'mod',
2396 'itemmodule' => 'assign',
2397 'iteminstance' => $assign->get_instance()->id,
2398 'courseid' => $course->id));
2400 $gradeitem->set_hidden(1, false);
2402 // No feedback should be available because the grade is hidden.
2403 $this->setUser($student);
2404 $output = $assign->view_student_summary($student, true);
2405 $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2409 * Test reopen behavior when in "Manual" mode.
2411 public function test_attempt_reopen_method_manual() {
2412 global $PAGE;
2414 $this->resetAfterTest();
2415 $course = $this->getDataGenerator()->create_course();
2416 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2417 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2419 $assign = $this->create_instance($course, [
2420 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2421 'maxattempts' => 3,
2422 'submissiondrafts' => 1,
2423 'assignsubmission_onlinetext_enabled' => 1,
2425 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2427 // Student should be able to see an add submission button.
2428 $this->setUser($student);
2429 $output = $assign->view_student_summary($student, true);
2430 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2432 // Add a submission.
2433 $this->add_submission($student, $assign);
2434 $this->submit_for_grading($student, $assign);
2436 // Verify the student cannot make changes to the submission.
2437 $output = $assign->view_student_summary($student, true);
2438 $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2440 // Mark the submission.
2441 $this->mark_submission($teacher, $assign, $student);
2443 // Check the student can see the grade.
2444 $this->setUser($student);
2445 $output = $assign->view_student_summary($student, true);
2446 $this->assertNotEquals(false, strpos($output, '50.0'));
2448 // Allow the student another attempt.
2449 $teacher->ignoresesskey = true;
2450 $this->setUser($teacher);
2451 $result = $assign->testable_process_add_attempt($student->id);
2452 $this->assertEquals(true, $result);
2454 // Check that the previous attempt is now in the submission history table.
2455 $this->setUser($student);
2456 $output = $assign->view_student_summary($student, true);
2457 // Need a better check.
2458 $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2460 // Check that the student now has a button for Add a new attempt".
2461 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2462 // Check that the student now does not have a button for Submit.
2463 $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2465 // Check that the student now has a submission history.
2466 $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2468 $this->setUser($teacher);
2469 // Check that the grading table loads correctly and contains this user.
2470 // This is also testing that we do not get duplicate rows in the grading table.
2471 $gradingtable = new assign_grading_table($assign, 100, '', 0, true);
2472 $output = $assign->get_renderer()->render($gradingtable);
2473 $this->assertEquals(true, strpos($output, $student->lastname));
2475 // Should be 1 not 2.
2476 $this->assertEquals(1, $assign->count_submissions());
2477 $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2478 $this->assertEquals(0, $assign->count_submissions_need_grading());
2479 $this->assertEquals(1, $assign->count_grades());
2481 // Change max attempts to unlimited.
2482 $formdata = clone($assign->get_instance());
2483 $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2484 $formdata->instance = $formdata->id;
2485 $assign->update_instance($formdata);
2487 // Mark the submission again.
2488 $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2490 // Check the grade exists.
2491 $this->setUser($teacher);
2492 $grades = $assign->get_user_grades_for_gradebook($student->id);
2493 $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2495 // Check we can reopen still.
2496 $result = $assign->testable_process_add_attempt($student->id);
2497 $this->assertEquals(true, $result);
2499 // Should no longer have a grade because there is no grade for the latest attempt.
2500 $grades = $assign->get_user_grades_for_gradebook($student->id);
2501 $this->assertEmpty($grades);
2505 * Test reopen behavior when in "Reopen until pass" mode.
2507 public function test_attempt_reopen_method_untilpass() {
2508 global $PAGE;
2510 $this->resetAfterTest();
2511 $course = $this->getDataGenerator()->create_course();
2512 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2513 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2515 $assign = $this->create_instance($course, [
2516 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2517 'maxattempts' => 3,
2518 'submissiondrafts' => 1,
2519 'assignsubmission_onlinetext_enabled' => 1,
2521 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2523 // Set grade to pass to 80.
2524 $gradeitem = $assign->get_grade_item();
2525 $gradeitem->gradepass = '80.0';
2526 $gradeitem->update();
2528 // Student should be able to see an add submission button.
2529 $this->setUser($student);
2530 $output = $assign->view_student_summary($student, true);
2531 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2533 // Add a submission.
2534 $this->add_submission($student, $assign);
2535 $this->submit_for_grading($student, $assign);
2537 // Verify the student cannot make a new attempt.
2538 $output = $assign->view_student_summary($student, true);
2539 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2541 // Mark the submission as non-passing.
2542 $this->mark_submission($teacher, $assign, $student, 50.0);
2544 // Check the student can see the grade.
2545 $this->setUser($student);
2546 $output = $assign->view_student_summary($student, true);
2547 $this->assertNotEquals(false, strpos($output, '50.0'));
2549 // Check that the student now has a button for Add a new attempt.
2550 $output = $assign->view_student_summary($student, true);
2551 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2553 // Check that the student now does not have a button for Submit.
2554 $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2556 // Check that the student now has a submission history.
2557 $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2559 // Add a second submission.
2560 $this->add_submission($student, $assign);
2561 $this->submit_for_grading($student, $assign);
2563 // Mark the submission as passing.
2564 $this->mark_submission($teacher, $assign, $student, 80.0);
2566 // Check that the student does not have a button for Add a new attempt.
2567 $this->setUser($student);
2568 $output = $assign->view_student_summary($student, true);
2569 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2571 // Re-mark the submission as not passing.
2572 $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2574 // Check that the student now has a button for Add a new attempt.
2575 $this->setUser($student);
2576 $output = $assign->view_student_summary($student, true);
2577 $this->assertMatchesRegularExpression('/' . get_string('addnewattempt', 'assign') . '/', $output);
2578 $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2581 public function test_attempt_reopen_method_untilpass_passing() {
2582 global $PAGE;
2584 $this->resetAfterTest();
2585 $course = $this->getDataGenerator()->create_course();
2586 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2587 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2589 $assign = $this->create_instance($course, [
2590 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2591 'maxattempts' => 3,
2592 'submissiondrafts' => 1,
2593 'assignsubmission_onlinetext_enabled' => 1,
2595 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2597 // Set grade to pass to 80.
2598 $gradeitem = $assign->get_grade_item();
2599 $gradeitem->gradepass = '80.0';
2600 $gradeitem->update();
2602 // Student should be able to see an add submission button.
2603 $this->setUser($student);
2604 $output = $assign->view_student_summary($student, true);
2605 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2607 // Add a submission as a student.
2608 $this->add_submission($student, $assign);
2609 $this->submit_for_grading($student, $assign);
2611 // Mark the submission as passing.
2612 $this->mark_submission($teacher, $assign, $student, 100.0);
2614 // Check the student can see the grade.
2615 $this->setUser($student);
2616 $output = $assign->view_student_summary($student, true);
2617 $this->assertNotEquals(false, strpos($output, '100.0'));
2619 // Check that the student does not have a button for Add a new attempt.
2620 $output = $assign->view_student_summary($student, true);
2621 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2624 public function test_attempt_reopen_method_untilpass_no_passing_requirement() {
2625 global $PAGE;
2627 $this->resetAfterTest();
2628 $course = $this->getDataGenerator()->create_course();
2629 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2630 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2632 $assign = $this->create_instance($course, [
2633 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2634 'maxattempts' => 3,
2635 'submissiondrafts' => 1,
2636 'assignsubmission_onlinetext_enabled' => 1,
2638 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2640 // Set grade to pass to 0, so that no attempts should reopen.
2641 $gradeitem = $assign->get_grade_item();
2642 $gradeitem->gradepass = '0';
2643 $gradeitem->update();
2645 // Student should be able to see an add submission button.
2646 $this->setUser($student);
2647 $output = $assign->view_student_summary($student, true);
2648 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2650 // Add a submission.
2651 $this->add_submission($student, $assign);
2652 $this->submit_for_grading($student, $assign);
2654 // Mark the submission with any grade.
2655 $this->mark_submission($teacher, $assign, $student, 0.0);
2657 // Check the student can see the grade.
2658 $this->setUser($student);
2659 $output = $assign->view_student_summary($student, true);
2660 $this->assertNotEquals(false, strpos($output, '0.0'));
2662 // Check that the student does not have a button for Add a new attempt.
2663 $output = $assign->view_student_summary($student, true);
2664 $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2668 * Test student visibility for each stage of the marking workflow.
2670 public function test_markingworkflow() {
2671 global $PAGE;
2673 $this->resetAfterTest();
2674 $course = $this->getDataGenerator()->create_course();
2675 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2676 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2678 $assign = $this->create_instance($course, [
2679 'markingworkflow' => 1,
2682 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2684 // Mark the submission and set to notmarked.
2685 $this->mark_submission($teacher, $assign, $student, 50.0, [
2686 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2689 // Check the student can't see the grade.
2690 $this->setUser($student);
2691 $output = $assign->view_student_summary($student, true);
2692 $this->assertEquals(false, strpos($output, '50.0'));
2694 // Make sure the grade isn't pushed to the gradebook.
2695 $grades = $assign->get_user_grades_for_gradebook($student->id);
2696 $this->assertEmpty($grades);
2698 // Mark the submission and set to inmarking.
2699 $this->mark_submission($teacher, $assign, $student, 50.0, [
2700 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2703 // Check the student can't see the grade.
2704 $this->setUser($student);
2705 $output = $assign->view_student_summary($student, true);
2706 $this->assertEquals(false, strpos($output, '50.0'));
2708 // Make sure the grade isn't pushed to the gradebook.
2709 $grades = $assign->get_user_grades_for_gradebook($student->id);
2710 $this->assertEmpty($grades);
2712 // Mark the submission and set to readyforreview.
2713 $this->mark_submission($teacher, $assign, $student, 50.0, [
2714 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2717 // Check the student can't see the grade.
2718 $this->setUser($student);
2719 $output = $assign->view_student_summary($student, true);
2720 $this->assertEquals(false, strpos($output, '50.0'));
2722 // Make sure the grade isn't pushed to the gradebook.
2723 $grades = $assign->get_user_grades_for_gradebook($student->id);
2724 $this->assertEmpty($grades);
2726 // Mark the submission and set to inreview.
2727 $this->mark_submission($teacher, $assign, $student, 50.0, [
2728 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2731 // Check the student can't see the grade.
2732 $this->setUser($student);
2733 $output = $assign->view_student_summary($student, true);
2734 $this->assertEquals(false, strpos($output, '50.0'));
2736 // Make sure the grade isn't pushed to the gradebook.
2737 $grades = $assign->get_user_grades_for_gradebook($student->id);
2738 $this->assertEmpty($grades);
2740 // Mark the submission and set to readyforrelease.
2741 $this->mark_submission($teacher, $assign, $student, 50.0, [
2742 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
2745 // Check the student can't see the grade.
2746 $this->setUser($student);
2747 $output = $assign->view_student_summary($student, true);
2748 $this->assertEquals(false, strpos($output, '50.0'));
2750 // Make sure the grade isn't pushed to the gradebook.
2751 $grades = $assign->get_user_grades_for_gradebook($student->id);
2752 $this->assertEmpty($grades);
2754 // Mark the submission and set to released.
2755 $this->mark_submission($teacher, $assign, $student, 50.0, [
2756 'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
2759 // Check the student can see the grade.
2760 $this->setUser($student);
2761 $output = $assign->view_student_summary($student, true);
2762 $this->assertNotEquals(false, strpos($output, '50.0'));
2764 // Make sure the grade is pushed to the gradebook.
2765 $grades = $assign->get_user_grades_for_gradebook($student->id);
2766 $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
2770 * Test that a student allocated a specific marker is only shown to that marker.
2772 public function test_markerallocation() {
2773 global $PAGE;
2775 $this->resetAfterTest();
2776 $course = $this->getDataGenerator()->create_course();
2777 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2778 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2779 $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2781 $assign = $this->create_instance($course, [
2782 'markingworkflow' => 1,
2783 'markingallocation' => 1
2786 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2788 // Allocate marker to submission.
2789 $this->mark_submission($teacher, $assign, $student, null, [
2790 'allocatedmarker' => $teacher->id,
2793 // Check the allocated marker can view the submission.
2794 $this->setUser($teacher);
2795 $users = $assign->list_participants(0, true);
2796 $this->assertEquals(1, count($users));
2797 $this->assertTrue(isset($users[$student->id]));
2799 $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2800 $context = context_module::instance($cm->id);
2801 $assign = new mod_assign_testable_assign($context, $cm, $course);
2803 // Check that other teachers can't view this submission.
2804 $this->setUser($otherteacher);
2805 $users = $assign->list_participants(0, true);
2806 $this->assertEquals(0, count($users));
2810 * Ensure that a teacher cannot submit for students as standard.
2812 public function test_teacher_submit_for_student() {
2813 global $PAGE;
2815 $this->resetAfterTest();
2816 $course = $this->getDataGenerator()->create_course();
2817 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2818 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2820 $assign = $this->create_instance($course, [
2821 'assignsubmission_onlinetext_enabled' => 1,
2822 'submissiondrafts' => 1,
2825 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2827 // Add a submission but do not submit.
2828 $this->add_submission($student, $assign, 'Student submission text');
2830 $this->setUser($student);
2831 $output = $assign->view_student_summary($student, true);
2832 $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2834 // Check that a teacher can not edit the submission as they do not have the capability.
2835 $this->setUser($teacher);
2836 $this->expectException('moodle_exception');
2837 $this->expectExceptionMessage('error/nopermission');
2838 $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2842 * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
2844 public function test_teacher_submit_for_student_with_capability() {
2845 global $PAGE;
2847 $this->resetAfterTest();
2848 $course = $this->getDataGenerator()->create_course();
2849 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2850 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2851 $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2853 $assign = $this->create_instance($course, [
2854 'assignsubmission_onlinetext_enabled' => 1,
2855 'submissiondrafts' => 1,
2858 // Add the required capability.
2859 $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2860 assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2861 role_assign($roleid, $teacher->id, $assign->get_context()->id);
2862 accesslib_clear_all_caches_for_unit_testing();
2864 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2866 // Add a submission but do not submit.
2867 $this->add_submission($student, $assign, 'Student submission text');
2869 $this->setUser($student);
2870 $output = $assign->view_student_summary($student, true);
2871 $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2873 // Check that a teacher can edit the submission.
2874 $this->setUser($teacher);
2875 $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2877 $this->setUser($student);
2878 $output = $assign->view_student_summary($student, true);
2879 $this->assertStringNotContainsString('Student submission text', $output, 'Contains student submission text');
2880 $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains teacher edited submission text');
2882 // Check that the teacher can submit the students work.
2883 $this->setUser($teacher);
2884 $this->submit_for_grading($student, $assign, [], false);
2886 // Revert to draft so the student can edit it.
2887 $assign->revert_to_draft($student->id);
2889 $this->setUser($student);
2891 // Check that the submission text was saved.
2892 $output = $assign->view_student_summary($student, true);
2893 $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains student submission text');
2895 // Check that the student can submit their work.
2896 $this->submit_for_grading($student, $assign, []);
2898 $output = $assign->view_student_summary($student, true);
2899 $this->assertStringNotContainsString(get_string('addsubmission', 'assign'), $output);
2901 // An editing teacher without the extra role should still be able to revert to draft.
2902 $this->setUser($otherteacher);
2904 // Revert to draft so the submission is editable.
2905 $assign->revert_to_draft($student->id);
2909 * Ensure that disabling submit after the cutoff date works as expected.
2911 public function test_disable_submit_after_cutoff_date() {
2912 global $PAGE;
2914 $this->resetAfterTest();
2915 $course = $this->getDataGenerator()->create_course();
2916 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2918 $now = time();
2919 $tomorrow = $now + DAYSECS;
2920 $lastweek = $now - (7 * DAYSECS);
2921 $yesterday = $now - DAYSECS;
2923 $this->setAdminUser();
2924 $assign = $this->create_instance($course, [
2925 'duedate' => $yesterday,
2926 'cutoffdate' => $tomorrow,
2927 'assignsubmission_onlinetext_enabled' => 1,
2930 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2932 // Student should be able to see an add submission button.
2933 $this->setUser($student);
2934 $output = $assign->view_student_summary($student, true);
2935 $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2937 // Add a submission but don't submit now.
2938 $this->add_submission($student, $assign);
2940 // Create another instance with cut-off and due-date already passed.
2941 $this->setAdminUser();
2942 $assign = $this->create_instance($course, [
2943 'duedate' => $lastweek,
2944 'cutoffdate' => $yesterday,
2945 'assignsubmission_onlinetext_enabled' => 1,
2948 $this->setUser($student);
2949 $output = $assign->view_student_summary($student, true);
2950 $this->assertStringNotContainsString($output, get_string('editsubmission', 'assign'),
2951 'Should not be able to edit after cutoff date.');
2952 $this->assertStringNotContainsString($output, get_string('submitassignment', 'assign'),
2953 'Should not be able to submit after cutoff date.');
2957 * Testing for submission comment plugin settings.
2959 * @dataProvider submission_plugin_settings_provider
2960 * @param bool $globalenabled
2961 * @param array $instanceconfig
2962 * @param bool $isenabled
2964 public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled) {
2965 global $CFG;
2967 $this->resetAfterTest();
2968 $course = $this->getDataGenerator()->create_course();
2970 $CFG->usecomments = $globalenabled;
2971 $assign = $this->create_instance($course, $instanceconfig);
2972 $plugin = $assign->get_submission_plugin_by_type('comments');
2973 $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
2976 public function submission_plugin_settings_provider() {
2977 return [
2978 'CFG->usecomments true, empty config => Enabled by default' => [
2979 true,
2981 true,
2983 'CFG->usecomments true, config enabled => Comments enabled' => [
2984 true,
2986 'assignsubmission_comments_enabled' => 1,
2988 true,
2990 'CFG->usecomments true, config idisabled => Comments enabled' => [
2991 true,
2993 'assignsubmission_comments_enabled' => 0,
2995 true,
2997 'CFG->usecomments false, empty config => Disabled by default' => [
2998 false,
3000 false,
3002 'CFG->usecomments false, config enabled => Comments disabled' => [
3003 false,
3005 'assignsubmission_comments_enabled' => 1,
3007 false,
3009 'CFG->usecomments false, config disabled => Comments disabled' => [
3010 false,
3012 'assignsubmission_comments_enabled' => 0,
3014 false,
3020 * Testing for comment inline settings
3022 public function test_feedback_comment_commentinline() {
3023 global $CFG, $USER;
3025 $this->resetAfterTest();
3026 $course = $this->getDataGenerator()->create_course();
3027 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3028 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3030 $sourcetext = "Hello!
3032 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3034 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3035 Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3037 External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
3038 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
3039 Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
3040 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
3041 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
3042 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3045 $this->setUser($teacher);
3046 $assign = $this->create_instance($course, [
3047 'assignsubmission_onlinetext_enabled' => 1,
3048 'assignfeedback_comments_enabled' => 1,
3049 'assignfeedback_comments_commentinline' => 1,
3052 $this->setUser($student);
3054 // Add a submission but don't submit now.
3055 $this->add_submission($student, $assign, $sourcetext);
3057 $this->setUser($teacher);
3059 $data = new stdClass();
3060 require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3061 $pagination = [
3062 'userid' => $student->id,
3063 'rownum' => 0,
3064 'last' => true,
3065 'useridlistid' => $assign->get_useridlist_key_id(),
3066 'attemptnumber' => 0,
3068 $formparams = array($assign, $data, $pagination);
3069 $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
3071 // We need to get the URL these will be transformed to.
3072 $context = context_user::instance($USER->id);
3073 $itemid = $data->assignfeedbackcomments_editor['itemid'];
3074 $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid;
3076 // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
3077 $filteredtext = "Hello!
3079 I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3081 URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3082 Plugin url outside a tag: $url/logo-240x60.gif
3084 External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3085 External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3086 Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" />
3087 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" />
3088 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a>
3089 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3092 $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3096 * Testing for feedback comment plugin settings.
3098 * @dataProvider feedback_plugin_settings_provider
3099 * @param array $instanceconfig
3100 * @param bool $isenabled
3102 public function test_feedback_plugin_settings($instanceconfig, $isenabled) {
3103 $this->resetAfterTest();
3104 $course = $this->getDataGenerator()->create_course();
3106 $assign = $this->create_instance($course, $instanceconfig);
3107 $plugin = $assign->get_feedback_plugin_by_type('comments');
3108 $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3111 public function feedback_plugin_settings_provider() {
3112 return [
3113 'No configuration => disabled' => [
3115 false,
3117 'Actively disabled' => [
3119 'assignfeedback_comments_enabled' => 0,
3121 false,
3123 'Actively enabled' => [
3125 'assignfeedback_comments_enabled' => 1,
3127 true,
3133 * Testing if gradebook feedback plugin is enabled.
3135 public function test_is_gradebook_feedback_enabled() {
3136 $this->resetAfterTest();
3137 $course = $this->getDataGenerator()->create_course();
3138 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3139 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3141 $adminconfig = get_config('assign');
3142 $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3144 // Create assignment with gradebook feedback enabled and grade = 0.
3145 $assign = $this->create_instance($course, [
3146 "{$gradebookplugin}_enabled" => 1,
3147 'grades' => 0,
3150 // Get gradebook feedback plugin.
3151 $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3152 $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3153 $this->assertEquals(1, $plugin->is_enabled('enabled'));
3154 $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3158 * Testing if gradebook feedback plugin is disabled.
3160 public function test_is_gradebook_feedback_disabled() {
3161 $this->resetAfterTest();
3162 $course = $this->getDataGenerator()->create_course();
3163 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3164 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3166 $adminconfig = get_config('assign');
3167 $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3169 // Create assignment with gradebook feedback disabled and grade = 0.
3170 $assign = $this->create_instance($course, [
3171 "{$gradebookplugin}_enabled" => 0,
3172 'grades' => 0,
3175 $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3176 $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3177 $this->assertEquals(0, $plugin->is_enabled('enabled'));
3181 * Testing can_edit_submission.
3183 public function test_can_edit_submission() {
3184 $this->resetAfterTest();
3185 $course = $this->getDataGenerator()->create_course();
3186 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3187 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3188 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3190 $assign = $this->create_instance($course, [
3191 'assignsubmission_onlinetext_enabled' => 1,
3192 'submissiondrafts' => 1,
3195 // Check student can edit their own submission.
3196 $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3198 // Check student cannot edit others submission.
3199 $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3201 // Check teacher cannot (by default) edit a students submission.
3202 $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3206 * Testing can_edit_submission with the editothersubmission capability.
3208 public function test_can_edit_submission_with_editothersubmission() {
3209 $this->resetAfterTest();
3210 $course = $this->getDataGenerator()->create_course();
3211 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3212 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3213 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3215 $assign = $this->create_instance($course, [
3216 'assignsubmission_onlinetext_enabled' => 1,
3217 'submissiondrafts' => 1,
3220 // Add the required capability to edit a student submission.
3221 $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3222 assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3223 role_assign($roleid, $teacher->id, $assign->get_context()->id);
3224 accesslib_clear_all_caches_for_unit_testing();
3226 // Check student can edit their own submission.
3227 $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3229 // Check student cannot edit others submission.
3230 $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3232 // Retest - should now have access.
3233 $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3237 * Testing can_edit_submission
3239 public function test_can_edit_submission_separategroups() {
3240 $this->resetAfterTest();
3241 $course = $this->getDataGenerator()->create_course();
3242 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3244 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3245 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3246 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3247 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3249 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3250 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3251 groups_assign_grouping($grouping->id, $group1->id);
3252 groups_add_member($group1, $student1);
3253 groups_add_member($group1, $student2);
3255 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3256 groups_assign_grouping($grouping->id, $group2->id);
3257 groups_add_member($group2, $student3);
3258 groups_add_member($group2, $student4);
3260 $assign = $this->create_instance($course, [
3261 'assignsubmission_onlinetext_enabled' => 1,
3262 'submissiondrafts' => 1,
3263 'groupingid' => $grouping->id,
3264 'groupmode' => SEPARATEGROUPS,
3267 // Verify a student does not have the ability to edit submissions for other users.
3268 $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3269 $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id));
3270 $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3271 $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3275 * Testing can_edit_submission
3277 public function test_can_edit_submission_separategroups_with_editothersubmission() {
3278 $this->resetAfterTest();
3279 $course = $this->getDataGenerator()->create_course();
3280 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3282 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3283 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3284 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3285 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3287 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3288 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3289 groups_assign_grouping($grouping->id, $group1->id);
3290 groups_add_member($group1, $student1);
3291 groups_add_member($group1, $student2);
3293 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3294 groups_assign_grouping($grouping->id, $group2->id);
3295 groups_add_member($group2, $student3);
3296 groups_add_member($group2, $student4);
3298 $assign = $this->create_instance($course, [
3299 'assignsubmission_onlinetext_enabled' => 1,
3300 'submissiondrafts' => 1,
3301 'groupingid' => $grouping->id,
3302 'groupmode' => SEPARATEGROUPS,
3305 // Add the capability to the new assignment for student 1.
3306 $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3307 assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3308 role_assign($roleid, $student1->id, $assign->get_context()->id);
3309 accesslib_clear_all_caches_for_unit_testing();
3311 // Verify student1 has the ability to edit submissions for other users in their group, but not other groups.
3312 $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3313 $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id));
3314 $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3315 $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3317 // Verify other students do not have the ability to edit submissions for other users.
3318 $this->assertTrue($assign->can_edit_submission($student2->id, $student2->id));
3319 $this->assertFalse($assign->can_edit_submission($student1->id, $student2->id));
3320 $this->assertFalse($assign->can_edit_submission($student3->id, $student2->id));
3321 $this->assertFalse($assign->can_edit_submission($student4->id, $student2->id));
3325 * Test if the view blind details capability works
3327 public function test_can_view_blind_details() {
3328 global $DB;
3330 $this->resetAfterTest();
3331 $course = $this->getDataGenerator()->create_course();
3332 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3333 $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
3334 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3336 $assign = $this->create_instance($course, [
3337 'blindmarking' => 1,
3340 $this->assertTrue($assign->is_blind_marking());
3342 // Test student names are hidden to teacher.
3343 $this->setUser($teacher);
3344 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
3345 $output = $assign->get_renderer()->render($gradingtable);
3346 $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign'))); // "Participant" is somewhere on the page.
3347 $this->assertEquals(false, strpos($output, fullname($student))); // Students full name doesn't appear.
3349 // Test student names are visible to manager.
3350 $this->setUser($manager);
3351 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
3352 $output = $assign->get_renderer()->render($gradingtable);
3353 $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
3354 $this->assertEquals(true, strpos($output, fullname($student)));
3358 * Testing get_shared_group_members
3360 public function test_get_shared_group_members() {
3361 $this->resetAfterTest();
3362 $course = $this->getDataGenerator()->create_course();
3363 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3365 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3366 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3367 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3368 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3370 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3371 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3372 groups_assign_grouping($grouping->id, $group1->id);
3373 groups_add_member($group1, $student1);
3374 groups_add_member($group1, $student2);
3376 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3377 groups_assign_grouping($grouping->id, $group2->id);
3378 groups_add_member($group2, $student3);
3379 groups_add_member($group2, $student4);
3381 $assign = $this->create_instance($course, [
3382 'groupingid' => $grouping->id,
3383 'groupmode' => SEPARATEGROUPS,
3386 $cm = $assign->get_course_module();
3388 // Get shared group members for students 0 and 1.
3389 $groupmembers = $assign->get_shared_group_members($cm, $student1->id);
3390 $this->assertCount(2, $groupmembers);
3391 $this->assertContainsEquals($student1->id, $groupmembers);
3392 $this->assertContainsEquals($student2->id, $groupmembers);
3394 $groupmembers = $assign->get_shared_group_members($cm, $student2->id);
3395 $this->assertCount(2, $groupmembers);
3396 $this->assertContainsEquals($student1->id, $groupmembers);
3397 $this->assertContainsEquals($student2->id, $groupmembers);
3399 $groupmembers = $assign->get_shared_group_members($cm, $student3->id);
3400 $this->assertCount(2, $groupmembers);
3401 $this->assertContainsEquals($student3->id, $groupmembers);
3402 $this->assertContainsEquals($student4->id, $groupmembers);
3404 $groupmembers = $assign->get_shared_group_members($cm, $student4->id);
3405 $this->assertCount(2, $groupmembers);
3406 $this->assertContainsEquals($student3->id, $groupmembers);
3407 $this->assertContainsEquals($student4->id, $groupmembers);
3411 * Testing get_shared_group_members
3413 public function test_get_shared_group_members_override() {
3414 $this->resetAfterTest();
3415 $course = $this->getDataGenerator()->create_course();
3416 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3418 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3419 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3420 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3421 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3423 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3424 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3425 groups_assign_grouping($grouping->id, $group1->id);
3426 groups_add_member($group1, $student1);
3427 groups_add_member($group1, $student2);
3429 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3430 groups_assign_grouping($grouping->id, $group2->id);
3431 groups_add_member($group2, $student3);
3432 groups_add_member($group2, $student4);
3434 $assign = $this->create_instance($course, [
3435 'groupingid' => $grouping->id,
3436 'groupmode' => SEPARATEGROUPS,
3439 $cm = $assign->get_course_module();
3441 // Add the capability to access allgroups for one of the students.
3442 $roleid = create_role('Access all groups role', 'accessallgroupsrole', '');
3443 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $assign->get_context()->id);
3444 role_assign($roleid, $student1->id, $assign->get_context()->id);
3445 accesslib_clear_all_caches_for_unit_testing();
3447 // Get shared group members for students 0 and 1.
3448 $groupmembers = $assign->get_shared_group_members($cm, $student1->id);
3449 $this->assertCount(4, $groupmembers);
3450 $this->assertContainsEquals($student1->id, $groupmembers);
3451 $this->assertContainsEquals($student2->id, $groupmembers);
3452 $this->assertContainsEquals($student3->id, $groupmembers);
3453 $this->assertContainsEquals($student4->id, $groupmembers);
3455 $groupmembers = $assign->get_shared_group_members($cm, $student2->id);
3456 $this->assertCount(2, $groupmembers);
3457 $this->assertContainsEquals($student1->id, $groupmembers);
3458 $this->assertContainsEquals($student2->id, $groupmembers);
3460 $groupmembers = $assign->get_shared_group_members($cm, $student3->id);
3461 $this->assertCount(2, $groupmembers);
3462 $this->assertContainsEquals($student3->id, $groupmembers);
3463 $this->assertContainsEquals($student4->id, $groupmembers);
3465 $groupmembers = $assign->get_shared_group_members($cm, $student4->id);
3466 $this->assertCount(2, $groupmembers);
3467 $this->assertContainsEquals($student3->id, $groupmembers);
3468 $this->assertContainsEquals($student4->id, $groupmembers);
3472 * Test get plugins file areas
3474 public function test_get_plugins_file_areas() {
3475 global $DB;
3477 $this->resetAfterTest();
3478 $course = $this->getDataGenerator()->create_course();
3479 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3481 $assign = $this->create_instance($course);
3483 // Test that all the submission and feedback plugins are returning the expected file aras.
3484 $usingfilearea = 0;
3485 $coreplugins = core_plugin_manager::standard_plugins_list('assignsubmission');
3486 foreach ($assign->get_submission_plugins() as $plugin) {
3487 $type = $plugin->get_type();
3488 if (!in_array($type, $coreplugins)) {
3489 continue;
3491 $fileareas = $plugin->get_file_areas();
3493 if ($type == 'onlinetext') {
3494 $this->assertEquals(array('submissions_onlinetext' => 'Online text'), $fileareas);
3495 $usingfilearea++;
3496 } else if ($type == 'file') {
3497 $this->assertEquals(array('submission_files' => 'File submissions'), $fileareas);
3498 $usingfilearea++;
3499 } else {
3500 $this->assertEmpty($fileareas);
3503 $this->assertEquals(2, $usingfilearea);
3505 $usingfilearea = 0;
3506 $coreplugins = core_plugin_manager::standard_plugins_list('assignfeedback');
3507 foreach ($assign->get_feedback_plugins() as $plugin) {
3508 $type = $plugin->get_type();
3509 if (!in_array($type, $coreplugins)) {
3510 continue;
3512 $fileareas = $plugin->get_file_areas();
3514 if ($type == 'editpdf') {
3515 $this->assertEquals(array('download' => 'Annotate PDF'), $fileareas);
3516 $usingfilearea++;
3517 } else if ($type == 'file') {
3518 $this->assertEquals(array('feedback_files' => 'Feedback files'), $fileareas);
3519 $usingfilearea++;
3520 } else if ($type == 'comments') {
3521 $this->assertEquals(array('feedback' => 'Feedback comments'), $fileareas);
3522 $usingfilearea++;
3523 } else {
3524 $this->assertEmpty($fileareas);
3527 $this->assertEquals(3, $usingfilearea);
3531 * Test override exists
3533 * This function needs to obey the group override logic as per the assign grading table and
3534 * the overview block.
3536 public function test_override_exists() {
3537 global $DB;
3539 $this->resetAfterTest();
3540 $course = $this->getDataGenerator()->create_course();
3541 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3543 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3544 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3545 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3548 // Data:
3549 // - student1 => group A only
3550 // - student2 => group B only
3551 // - student3 => Group A + Group B (No user override)
3552 // - student4 => Group A + Group B (With user override)
3553 // - student4 => No groups.
3554 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3555 groups_add_member($group1, $student1);
3557 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3558 groups_add_member($group2, $student2);
3560 $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3561 groups_add_member($group1, $student3);
3562 groups_add_member($group2, $student3);
3564 $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3565 groups_add_member($group1, $student4);
3566 groups_add_member($group2, $student4);
3568 $student5 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3570 $assign = $this->create_instance($course);
3571 $instance = $assign->get_instance();
3573 // Overrides for each of the groups, and a user override.
3574 $overrides = [
3575 (object) [
3576 // Override for group 1, highest priority (numerically lowest sortorder).
3577 'assignid' => $instance->id,
3578 'groupid' => $group1->id,
3579 'userid' => null,
3580 'sortorder' => 1,
3581 'allowsubmissionsfromdate' => 1,
3582 'duedate' => 2,
3583 'cutoffdate' => 3
3585 (object) [
3586 // Override for group 2, lower priority (numerically higher sortorder).
3587 'assignid' => $instance->id,
3588 'groupid' => $group2->id,
3589 'userid' => null,
3590 'sortorder' => 2,
3591 'allowsubmissionsfromdate' => 5,
3592 'duedate' => 6,
3593 'cutoffdate' => 6
3595 (object) [
3596 // User override.
3597 'assignid' => $instance->id,
3598 'groupid' => null,
3599 'userid' => $student3->id,
3600 'sortorder' => null,
3601 'allowsubmissionsfromdate' => 7,
3602 'duedate' => 8,
3603 'cutoffdate' => 9
3607 foreach ($overrides as &$override) {
3608 $override->id = $DB->insert_record('assign_overrides', $override);
3611 // User only in group 1 should see the group 1 override.
3612 $this->assertEquals($overrides[0], $assign->override_exists($student1->id));
3614 // User only in group 2 should see the group 2 override.
3615 $this->assertEquals($overrides[1], $assign->override_exists($student2->id));
3617 // User only in both groups with an override should see the user override as it has higher priority.
3618 $this->assertEquals($overrides[2], $assign->override_exists($student3->id));
3620 // User only in both groups with no override should see the group 1 override as it has higher priority.
3621 $this->assertEquals($overrides[0], $assign->override_exists($student4->id));
3623 // User with no overrides shoudl get nothing.
3624 $override = $assign->override_exists($student5->id);
3625 $this->assertNull($override->duedate);
3626 $this->assertNull($override->cutoffdate);
3627 $this->assertNull($override->allowsubmissionsfromdate);
3631 * Test the quicksave grades processor
3633 public function test_process_save_quick_grades() {
3634 $this->resetAfterTest();
3635 $course = $this->getDataGenerator()->create_course();
3636 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3637 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3639 $teacher->ignoresesskey = true;
3640 $this->setUser($teacher);
3641 $assign = $this->create_instance($course, [
3642 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
3645 // Initially grade the user.
3646 $grade = (object) [
3647 'attemptnumber' => '',
3648 'timemodified' => '',
3650 $data = [
3651 "grademodified_{$student->id}" => $grade->timemodified,
3652 "gradeattempt_{$student->id}" => $grade->attemptnumber,
3653 "quickgrade_{$student->id}" => '60.0',
3656 $result = $assign->testable_process_save_quick_grades($data);
3657 $this->assertStringContainsString(get_string('quickgradingchangessaved', 'assign'), $result);
3658 $grade = $assign->get_user_grade($student->id, false);
3659 $this->assertEquals(60.0, $grade->grade);
3661 // Attempt to grade with a past attempts grade info.
3662 $assign->testable_process_add_attempt($student->id);
3663 $data = array(
3664 'grademodified_' . $student->id => $grade->timemodified,
3665 'gradeattempt_' . $student->id => $grade->attemptnumber,
3666 'quickgrade_' . $student->id => '50.0'
3668 $result = $assign->testable_process_save_quick_grades($data);
3669 $this->assertStringContainsString(get_string('errorrecordmodified', 'assign'), $result);
3670 $grade = $assign->get_user_grade($student->id, false);
3671 $this->assertFalse($grade);
3673 // Attempt to grade a the attempt.
3674 $submission = $assign->get_user_submission($student->id, false);
3675 $data = array(
3676 'grademodified_' . $student->id => '',
3677 'gradeattempt_' . $student->id => $submission->attemptnumber,
3678 'quickgrade_' . $student->id => '40.0'
3680 $result = $assign->testable_process_save_quick_grades($data);
3681 $this->assertStringContainsString(get_string('quickgradingchangessaved', 'assign'), $result);
3682 $grade = $assign->get_user_grade($student->id, false);
3683 $this->assertEquals(40.0, $grade->grade);
3685 // Catch grade update conflicts.
3686 // Save old data for later.
3687 $pastdata = $data;
3688 // Update the grade the 'good' way.
3689 $data = array(
3690 'grademodified_' . $student->id => $grade->timemodified,
3691 'gradeattempt_' . $student->id => $grade->attemptnumber,
3692 'quickgrade_' . $student->id => '30.0'
3694 $result = $assign->testable_process_save_quick_grades($data);
3695 $this->assertStringContainsString(get_string('quickgradingchangessaved', 'assign'), $result);
3696 $grade = $assign->get_user_grade($student->id, false);
3697 $this->assertEquals(30.0, $grade->grade);
3699 // Now update using 'old' data. Should fail.
3700 $result = $assign->testable_process_save_quick_grades($pastdata);
3701 $this->assertStringContainsString(get_string('errorrecordmodified', 'assign'), $result);
3702 $grade = $assign->get_user_grade($student->id, false);
3703 $this->assertEquals(30.0, $grade->grade);
3707 * Test updating activity completion when submitting an assessment.
3709 public function test_update_activity_completion_records_solitary_submission() {
3710 $this->resetAfterTest();
3712 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
3713 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3714 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3716 $this->setUser($teacher);
3717 $assign = $this->create_instance($course, [
3718 'grade' => 100,
3719 'completion' => COMPLETION_TRACKING_AUTOMATIC,
3720 'requireallteammemberssubmit' => 0,
3722 $cm = $assign->get_course_module();
3724 // Submit the assignment as the student.
3725 $this->add_submission($student, $assign);
3727 // Check that completion is not met yet.
3728 $completion = new completion_info($course);
3729 $completiondata = $completion->get_data($cm, false, $student->id);
3730 $this->assertEquals(0, $completiondata->completionstate);
3732 // Update to mark as complete.
3733 $submission = $assign->get_user_submission($student->id, true);
3734 $assign->testable_update_activity_completion_records(0, 0, $submission,
3735 $student->id, COMPLETION_COMPLETE, $completion);
3737 // Completion should now be met.
3738 $completiondata = $completion->get_data($cm, false, $student->id);
3739 $this->assertEquals(1, $completiondata->completionstate);
3743 * Test updating activity completion when submitting an assessment.
3745 public function test_update_activity_completion_records_team_submission() {
3746 $this->resetAfterTest();
3748 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
3749 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3750 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3751 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3753 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3754 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3756 groups_add_member($group1, $student);
3757 groups_add_member($group1, $otherstudent);
3759 $assign = $this->create_instance($course, [
3760 'grade' => 100,
3761 'completion' => COMPLETION_TRACKING_AUTOMATIC,
3762 'teamsubmission' => 1,
3765 $cm = $assign->get_course_module();
3767 $this->add_submission($student, $assign);
3768 $this->submit_for_grading($student, $assign, ['groupid' => $group1->id]);
3770 $completion = new completion_info($course);
3772 // Check that completion is not met yet.
3773 $completiondata = $completion->get_data($cm, false, $student->id);
3774 $this->assertEquals(0, $completiondata->completionstate);
3776 $completiondata = $completion->get_data($cm, false, $otherstudent->id);
3777 $this->assertEquals(0, $completiondata->completionstate);
3779 $submission = $assign->get_user_submission($student->id, true);
3780 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
3781 $submission->groupid = $group1->id;
3783 $assign->testable_update_activity_completion_records(1, 0, $submission, $student->id, COMPLETION_COMPLETE, $completion);
3785 // Completion should now be met.
3786 $completiondata = $completion->get_data($cm, false, $student->id);
3787 $this->assertEquals(1, $completiondata->completionstate);
3789 $completiondata = $completion->get_data($cm, false, $otherstudent->id);
3790 $this->assertEquals(1, $completiondata->completionstate);
3794 * Test updating activity completion when submitting an assessment for MDL-67126.
3796 public function test_update_activity_completion_records_team_submission_new() {
3797 $this->resetAfterTest();
3799 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
3800 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3801 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3802 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3804 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3805 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3807 groups_add_member($group1, $student);
3808 groups_add_member($group1, $otherstudent);
3810 $assign = $this->create_instance($course, [
3811 'submissiondrafts' => 0,
3812 'completion' => COMPLETION_TRACKING_AUTOMATIC,
3813 'completionsubmit' => 1,
3814 'teamsubmission' => 1,
3815 'assignsubmission_onlinetext_enabled' => 1
3818 $cm = $assign->get_course_module();
3820 $this->add_submission($student, $assign);
3822 $completion = new completion_info($course);
3824 // Completion should now be met.
3825 $completiondata = $completion->get_data($cm, false, $student->id);
3826 $this->assertEquals(1, $completiondata->completionstate);
3828 $completiondata = $completion->get_data($cm, false, $otherstudent->id);
3829 $this->assertEquals(1, $completiondata->completionstate);
3833 * Data provider for test_fix_null_grades
3834 * @return array[] Test data for test_fix_null_grades. Each element should contain grade, expectedcount and gradebookvalue
3836 public function fix_null_grades_provider() {
3837 return [
3838 'Negative less than one is errant' => [
3839 'grade' => -0.64,
3840 'gradebookvalue' => null,
3842 'Negative more than one is errant' => [
3843 'grade' => -30.18,
3844 'gradebookvalue' => null,
3846 'Negative one exactly is not errant, but shouldn\'t be pushed to gradebook' => [
3847 'grade' => ASSIGN_GRADE_NOT_SET,
3848 'gradebookvalue' => null,
3850 'Positive grade is not errant' => [
3851 'grade' => 1,
3852 'gradebookvalue' => 1,
3854 'Large grade is not errant' => [
3855 'grade' => 100,
3856 'gradebookvalue' => 100,
3858 'Zero grade is not errant' => [
3859 'grade' => 0,
3860 'gradebookvalue' => 0,
3866 * Test fix_null_grades
3867 * @param number $grade The grade we should set in the assign grading table.
3868 * @param number $expectedcount The finalgrade we expect in the gradebook after fixing the grades.
3869 * @dataProvider fix_null_grades_provider
3871 public function test_fix_null_grades($grade, $gradebookvalue) {
3872 global $DB;
3874 $this->resetAfterTest();
3876 $course = $this->getDataGenerator()->create_course();
3877 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3878 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3880 $this->setUser($teacher);
3881 $assign = $this->create_instance($course);
3883 // Try getting a student's grade. This will give a grade of -1.
3884 // Then we can override it with a bad negative grade.
3885 $assign->get_user_grade($student->id, true);
3887 // Set the grade to something errant.
3888 // We don't set the grader here, so we expect it to be -1 as a result.
3889 $DB->set_field(
3890 'assign_grades',
3891 'grade',
3892 $grade,
3894 'userid' => $student->id,
3895 'assignment' => $assign->get_instance()->id,
3898 $assign->grade = $grade;
3899 $assigntemp = clone $assign->get_instance();
3900 $assigntemp->cmidnumber = $assign->get_course_module()->idnumber;
3901 assign_update_grades($assigntemp);
3903 // Check that the gradebook was updated with the assign grade. So we can guarentee test results later on.
3904 $expectedgrade = $grade == -1 ? null : $grade; // Assign sends null to the gradebook for -1 grades.
3905 $gradegrade = grade_grade::fetch(array('userid' => $student->id, 'itemid' => $assign->get_grade_item()->id));
3906 $this->assertEquals(-1, $gradegrade->usermodified);
3907 $this->assertEquals($expectedgrade, $gradegrade->rawgrade);
3909 // Call fix_null_grades().
3910 $method = new ReflectionMethod(assign::class, 'fix_null_grades');
3911 $method->setAccessible(true);
3912 $result = $method->invoke($assign);
3914 $this->assertSame(true, $result);
3916 $gradegrade = grade_grade::fetch(array('userid' => $student->id, 'itemid' => $assign->get_grade_item()->id));
3918 $this->assertEquals(-1, $gradegrade->usermodified);
3919 $this->assertEquals($gradebookvalue, $gradegrade->finalgrade);
3921 // Check that the grade was updated in the gradebook by fix_null_grades.
3922 $this->assertEquals($gradebookvalue, $gradegrade->finalgrade);
3926 * Test grade override displays 'Graded' for students
3928 public function test_grade_submission_override() {
3929 global $DB, $PAGE, $OUTPUT;
3931 $this->resetAfterTest();
3933 $course = $this->getDataGenerator()->create_course();
3934 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3935 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3937 $this->setUser($teacher);
3938 $assign = $this->create_instance($course, [
3939 'assignsubmission_onlinetext_enabled' => 1,
3942 // Simulate adding a grade.
3943 $this->setUser($teacher);
3944 $data = new stdClass();
3945 $data->grade = '50.0';
3946 $assign->testable_apply_grade_to_user($data, $student->id, 0);
3948 // Set grade override.
3949 $gradegrade = grade_grade::fetch([
3950 'userid' => $student->id,
3951 'itemid' => $assign->get_grade_item()->id,
3954 // Check that grade submission is not overridden yet.
3955 $this->assertEquals(false, $gradegrade->is_overridden());
3957 // Simulate a submission.
3958 $this->setUser($student);
3959 $submission = $assign->get_user_submission($student->id, true);
3961 $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
3963 // Set override grade grade, and check that grade submission has been overridden.
3964 $gradegrade->set_overridden(true);
3965 $this->assertEquals(true, $gradegrade->is_overridden());
3967 // Check that submissionslocked message 'This assignment is not accepting submissions' does not appear for student.
3968 $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
3969 $output = $assign->get_renderer()->render($gradingtable);
3970 $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
3972 $assignsubmissionstatus = $assign->get_assign_submission_status_renderable($student, true);
3973 $output2 = $assign->get_renderer()->render($assignsubmissionstatus);
3975 // Check that submissionslocked 'This assignment is not accepting submissions' message does not appear for student.
3976 $this->assertStringNotContainsString(get_string('submissionslocked', 'assign'), $output2);
3977 // Check that submissionstatus_marked 'Graded' message does appear for student.
3978 $this->assertStringContainsString(get_string('submissionstatus_marked', 'assign'), $output2);
3982 * Test the result of get_filters is consistent.
3984 public function test_get_filters() {
3985 $this->resetAfterTest();
3987 $course = $this->getDataGenerator()->create_course();
3988 $assign = $this->create_instance($course);
3989 $valid = $assign->get_filters();
3991 $this->assertEquals(count($valid), 6);
3995 * Test assign->get_instance() for a number of cases, as defined in the data provider.
3997 * @dataProvider assign_get_instance_provider
3998 * @param array $courseconfig the config to use when creating the course.
3999 * @param array $assignconfig the config to use when creating the assignment.
4000 * @param array $enrolconfig the config to use when enrolling the user (this will be the active user).
4001 * @param array $expectedproperties an map containing the expected names and values for the assign instance data.
4003 public function test_assign_get_instance(array $courseconfig, array $assignconfig, array $enrolconfig,
4004 array $expectedproperties) {
4005 $this->resetAfterTest();
4007 set_config('enablecourserelativedates', true); // Enable relative dates at site level.
4009 $course = $this->getDataGenerator()->create_course($courseconfig);
4010 $assign = $this->create_instance($course, $assignconfig);
4011 $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig));
4013 $instance = $assign->get_instance($user->id);
4015 foreach ($expectedproperties as $propertyname => $propertyval) {
4016 $this->assertEquals($propertyval, $instance->$propertyname);
4021 * The test_assign_get_instance data provider.
4023 public function assign_get_instance_provider() {
4024 $timenow = time();
4026 // The get_default_instance() method shouldn't calculate any properties per-user. It should just return the record data.
4027 // We'll confirm this works for a few different user types anyway, just like we do for get_instance().
4028 return [
4029 'Teacher whose enrolment starts after the course start date, relative dates mode enabled' => [
4030 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4031 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4032 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4033 'startdate' => $timenow - 8 * DAYSECS],
4034 'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS]
4036 'Teacher whose enrolment starts before the course start date, relative dates mode enabled' => [
4037 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4038 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4039 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4040 'startdate' => $timenow - 12 * DAYSECS],
4041 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4043 'Teacher whose enrolment starts after the course start date, relative dates mode disabled' => [
4044 'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS],
4045 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4046 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4047 'startdate' => $timenow - 8 * DAYSECS],
4048 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4050 'Student whose enrolment starts after the course start date, relative dates mode enabled' => [
4051 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4052 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4053 'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4054 'startdate' => $timenow - 8 * DAYSECS],
4055 'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS]
4057 'Student whose enrolment starts before the course start date, relative dates mode enabled' => [
4058 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4059 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4060 'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4061 'startdate' => $timenow - 12 * DAYSECS],
4062 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4064 'Student whose enrolment starts after the course start date, relative dates mode disabled' => [
4065 'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS],
4066 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4067 'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4068 'startdate' => $timenow - 8 * DAYSECS],
4069 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4075 * Test assign->get_default_instance() for a number of cases, as defined in the date provider.
4077 * @dataProvider assign_get_default_instance_provider
4078 * @param array $courseconfig the config to use when creating the course.
4079 * @param array $assignconfig the config to use when creating the assignment.
4080 * @param array $enrolconfig the config to use when enrolling the user (this will be the active user).
4081 * @param array $expectedproperties an map containing the expected names and values for the assign instance data.
4083 public function test_assign_get_default_instance(array $courseconfig, array $assignconfig, array $enrolconfig,
4084 array $expectedproperties) {
4085 $this->resetAfterTest();
4087 set_config('enablecourserelativedates', true); // Enable relative dates at site level.
4089 $course = $this->getDataGenerator()->create_course($courseconfig);
4090 $assign = $this->create_instance($course, $assignconfig);
4091 $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig));
4093 $this->setUser($user);
4094 $defaultinstance = $assign->get_default_instance();
4096 foreach ($expectedproperties as $propertyname => $propertyval) {
4097 $this->assertEquals($propertyval, $defaultinstance->$propertyname);
4102 * The test_assign_get_default_instance data provider.
4104 public function assign_get_default_instance_provider() {
4105 $timenow = time();
4107 // The get_default_instance() method shouldn't calculate any properties per-user. It should just return the record data.
4108 // We'll confirm this works for a few different user types anyway, just like we do for get_instance().
4109 return [
4110 'Teacher whose enrolment starts after the course start date, relative dates mode enabled' => [
4111 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4112 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4113 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4114 'startdate' => $timenow - 8 * DAYSECS],
4115 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4117 'Teacher whose enrolment starts before the course start date, relative dates mode enabled' => [
4118 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4119 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4120 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4121 'startdate' => $timenow - 12 * DAYSECS],
4122 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4124 'Teacher whose enrolment starts after the course start date, relative dates mode disabled' => [
4125 'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS],
4126 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4127 'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4128 'startdate' => $timenow - 8 * DAYSECS],
4129 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4131 'Student whose enrolment starts after the course start date, relative dates mode enabled' => [
4132 'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4133 'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4134 'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4135 'startdate' => $timenow - 8 * DAYSECS],
4136 'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4142 * Test showing group override duedate for admin
4144 public function test_view_group_override() {
4145 global $DB, $PAGE;
4147 $this->resetAfterTest();
4148 $course = $this->getDataGenerator()->create_course();
4150 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
4151 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
4153 $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
4154 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4155 groups_add_member($group1, $student1);
4156 groups_add_member($group1, $teacher);
4158 $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
4159 groups_add_member($group2, $student2);
4161 $assign = $this->create_instance($course, [
4162 'groupmode' => 1,
4163 'duedate' => 1558999899,
4165 $instance = $assign->get_instance();
4167 // Overrides for two groups.
4168 $overrides = [
4169 (object) [
4170 'assignid' => $instance->id,
4171 'groupid' => $group1->id,
4172 'userid' => null,
4173 'sortorder' => 1,
4174 'duedate' => 1568990258,
4176 (object) [
4177 'assignid' => $instance->id,
4178 'groupid' => $group2->id,
4179 'userid' => null,
4180 'sortorder' => 2,
4181 'duedate' => 1559900258,
4185 foreach ($overrides as &$override) {
4186 $override->id = $DB->insert_record('assign_overrides', $override);
4189 $currenturl = new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id));
4190 $PAGE->set_url($currenturl);
4191 $output1 = '';
4192 // Other users should see duedate of the assignment.
4193 $this->setUser($student2);
4194 $summary = $assign->get_assign_grading_summary_renderable($group1->id);
4195 $output1 .= $assign->get_renderer()->render($summary);
4196 $this->assertStringContainsStringIgnoringCase('Tuesday, 28 May 2019, 7:31 AM', $output1);
4198 $output2 = '';
4199 // Teacher should be able to see all group override duedate.
4200 $this->setUser($teacher);
4201 $summary = $assign->get_assign_grading_summary_renderable($group1->id);
4202 $output2 .= $assign->get_renderer()->render($summary);
4203 $this->assertStringContainsStringIgnoringCase('Friday, 20 September 2019, 10:37 PM', $output2);
4204 $summary = $assign->get_assign_grading_summary_renderable($group2->id);
4205 $output3 = '';
4206 $output3 .= $assign->get_renderer()->render($summary);
4207 $this->assertStringContainsStringIgnoringCase('Friday, 7 June 2019, 5:37 PM', $output3);
4211 * Test that cron task uses task API to get its last run time.
4213 public function test_cron_use_task_api_to_get_lastruntime() {
4214 global $DB;
4215 $this->resetAfterTest();
4216 $course = $this->getDataGenerator()->create_course();
4218 // Create an assignment which allows submissions from 3 days ago.
4219 $assign1 = $this->create_instance($course, [
4220 'duedate' => time() + DAYSECS,
4221 'alwaysshowdescription' => 0,
4222 'allowsubmissionsfromdate' => time() - 3 * DAYSECS,
4223 'intro' => 'This one should not be re-created',
4226 // Create an assignment which allows submissions from 1 day ago.
4227 $assign2 = $this->create_instance($course, [
4228 'duedate' => time() + DAYSECS,
4229 'alwaysshowdescription' => 0,
4230 'allowsubmissionsfromdate' => time() - DAYSECS,
4231 'intro' => 'This one should be re-created',
4234 // Set last run time 2 days ago.
4235 $DB->set_field('task_scheduled', 'lastruntime', time() - 2 * DAYSECS, ['classname' => '\mod_assign\task\cron_task']);
4237 // Remove events to make sure cron will update calendar and re-create one of them.
4238 $params = array('modulename' => 'assign', 'instance' => $assign1->get_instance()->id);
4239 $DB->delete_records('event', $params);
4240 $params = array('modulename' => 'assign', 'instance' => $assign2->get_instance()->id);
4241 $DB->delete_records('event', $params);
4243 // Run cron.
4244 assign::cron();
4246 // Assert that calendar hasn't been updated for the first assignment as it's supposed to be
4247 // updated as part of previous cron runs (allowsubmissionsfromdate is less than lastruntime).
4248 $params = array('modulename' => 'assign', 'instance' => $assign1->get_instance()->id);
4249 $event1 = $DB->get_record('event', $params);
4250 $this->assertEmpty($event1);
4252 // Assert that calendar has been updated for the second assignment
4253 // because its allowsubmissionsfromdate is greater than lastruntime.
4254 $params = array('modulename' => 'assign', 'instance' => $assign2->get_instance()->id);
4255 $event2 = $DB->get_record('event', $params);
4256 $this->assertNotEmpty($event2);
4257 $this->assertSame('This one should be re-created', $event2->description);