1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17defined('MOODLE_INTERNAL') || die();
18
19global $CFG;
20
21require_once($CFG->dirroot . '/webservice/tests/helpers.php');
22require_once($CFG->dirroot . '/mod/assign/externallib.php');
23require_once(__DIR__ . '/fixtures/testable_assign.php');
24
25/**
26 * External mod assign functions unit tests
27 *
28 * @package mod_assign
29 * @category external
30 * @copyright 2012 Paul Charsley
31 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32 */
33class mod_assign_external_testcase extends externallib_advanced_testcase {
34
35    /**
36     * Test get_grades
37     */
38    public function test_get_grades() {
39        global $DB, $USER;
40
41        $this->resetAfterTest(true);
42        // Create a course and assignment.
43        $coursedata['idnumber'] = 'idnumbercourse';
44        $coursedata['fullname'] = 'Lightwork Course';
45        $coursedata['summary'] = 'Lightwork Course description';
46        $coursedata['summaryformat'] = FORMAT_MOODLE;
47        $course = self::getDataGenerator()->create_course($coursedata);
48
49        $assigndata['course'] = $course->id;
50        $assigndata['name'] = 'lightwork assignment';
51
52        $assign = self::getDataGenerator()->create_module('assign', $assigndata);
53
54        // Create a manual enrolment record.
55        $manualenroldata['enrol'] = 'manual';
56        $manualenroldata['status'] = 0;
57        $manualenroldata['courseid'] = $course->id;
58        $enrolid = $DB->insert_record('enrol', $manualenroldata);
59
60        // Create a teacher and give them capabilities.
61        $context = context_course::instance($course->id);
62        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
63        $context = context_module::instance($assign->cmid);
64        $this->assignUserCapability('mod/assign:viewgrades', $context->id, $roleid);
65
66        // Create the teacher's enrolment record.
67        $userenrolmentdata['status'] = 0;
68        $userenrolmentdata['enrolid'] = $enrolid;
69        $userenrolmentdata['userid'] = $USER->id;
70        $DB->insert_record('user_enrolments', $userenrolmentdata);
71
72        // Create a student and give them 2 grades (for 2 attempts).
73        $student = self::getDataGenerator()->create_user();
74
75        $submission = new stdClass();
76        $submission->assignment = $assign->id;
77        $submission->userid = $student->id;
78        $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
79        $submission->latest = 0;
80        $submission->attemptnumber = 0;
81        $submission->groupid = 0;
82        $submission->timecreated = time();
83        $submission->timemodified = time();
84        $DB->insert_record('assign_submission', $submission);
85
86        $grade = new stdClass();
87        $grade->assignment = $assign->id;
88        $grade->userid = $student->id;
89        $grade->timecreated = time();
90        $grade->timemodified = $grade->timecreated;
91        $grade->grader = $USER->id;
92        $grade->grade = 50;
93        $grade->attemptnumber = 0;
94        $DB->insert_record('assign_grades', $grade);
95
96        $submission = new stdClass();
97        $submission->assignment = $assign->id;
98        $submission->userid = $student->id;
99        $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
100        $submission->latest = 1;
101        $submission->attemptnumber = 1;
102        $submission->groupid = 0;
103        $submission->timecreated = time();
104        $submission->timemodified = time();
105        $DB->insert_record('assign_submission', $submission);
106
107        $grade = new stdClass();
108        $grade->assignment = $assign->id;
109        $grade->userid = $student->id;
110        $grade->timecreated = time();
111        $grade->timemodified = $grade->timecreated;
112        $grade->grader = $USER->id;
113        $grade->grade = 75;
114        $grade->attemptnumber = 1;
115        $DB->insert_record('assign_grades', $grade);
116
117        $assignmentids[] = $assign->id;
118        $result = mod_assign_external::get_grades($assignmentids);
119
120        // We need to execute the return values cleaning process to simulate the web service server.
121        $result = external_api::clean_returnvalue(mod_assign_external::get_grades_returns(), $result);
122
123        // Check that the correct grade information for the student is returned.
124        $this->assertEquals(1, count($result['assignments']));
125        $assignment = $result['assignments'][0];
126        $this->assertEquals($assign->id, $assignment['assignmentid']);
127        // Should only get the last grade for this student.
128        $this->assertEquals(1, count($assignment['grades']));
129        $grade = $assignment['grades'][0];
130        $this->assertEquals($student->id, $grade['userid']);
131        // Should be the last grade (not the first).
132        $this->assertEquals(75, $grade['grade']);
133    }
134
135    /**
136     * Test get_assignments
137     */
138    public function test_get_assignments() {
139        global $DB, $USER, $CFG;
140
141        $this->resetAfterTest(true);
142
143        // Enable multilang filter to on content and heading.
144        filter_set_global_state('multilang', TEXTFILTER_ON);
145        filter_set_applies_to_strings('multilang', 1);
146        // Set WS filtering.
147        $wssettings = external_settings::get_instance();
148        $wssettings->set_filter(true);
149
150        $category = self::getDataGenerator()->create_category(array(
151            'name' => 'Test category'
152        ));
153
154        // Create a course.
155        $course1 = self::getDataGenerator()->create_course(array(
156            'idnumber' => 'idnumbercourse1',
157            'fullname' => '<b>Lightwork Course 1</b>',      // Adding tags here to check that external_format_string works.
158            'shortname' => '<b>Lightwork Course 1</b>',     // Adding tags here to check that external_format_string works.
159            'summary' => 'Lightwork Course 1 description',
160            'summaryformat' => FORMAT_MOODLE,
161            'category' => $category->id
162        ));
163
164        // Create a second course, just for testing.
165        $course2 = self::getDataGenerator()->create_course(array(
166            'idnumber' => 'idnumbercourse2',
167            'fullname' => 'Lightwork Course 2',
168            'summary' => 'Lightwork Course 2 description',
169            'summaryformat' => FORMAT_MOODLE,
170            'category' => $category->id
171        ));
172
173        // Create the assignment module with links to a filerecord.
174        $assign1 = self::getDataGenerator()->create_module('assign', array(
175            'course' => $course1->id,
176            'name' => '<span lang="en" class="multilang">English</span><span lang="es" class="multilang">Español</span>',
177            'intro' => 'the assignment intro text here <a href="@@PLUGINFILE@@/intro.txt">link</a>',
178            'introformat' => FORMAT_HTML,
179            'markingworkflow' => 1,
180            'markingallocation' => 1
181        ));
182
183        // Add a file as assignment attachment.
184        $context = context_module::instance($assign1->cmid);
185        $filerecord = array('component' => 'mod_assign', 'filearea' => 'intro', 'contextid' => $context->id, 'itemid' => 0,
186                'filename' => 'intro.txt', 'filepath' => '/');
187        $fs = get_file_storage();
188        $fs->create_file_from_string($filerecord, 'Test intro file');
189
190        // Create manual enrolment record.
191        $enrolid = $DB->insert_record('enrol', (object)array(
192            'enrol' => 'manual',
193            'status' => 0,
194            'courseid' => $course1->id
195        ));
196
197        // Create the user and give them capabilities.
198        $context = context_course::instance($course1->id);
199        $roleid = $this->assignUserCapability('moodle/course:view', $context->id);
200        $context = context_module::instance($assign1->cmid);
201        $this->assignUserCapability('mod/assign:view', $context->id, $roleid);
202
203        // Create the user enrolment record.
204        $DB->insert_record('user_enrolments', (object)array(
205            'status' => 0,
206            'enrolid' => $enrolid,
207            'userid' => $USER->id
208        ));
209
210        // Add a file as assignment attachment.
211        $filerecord = array('component' => 'mod_assign', 'filearea' => ASSIGN_INTROATTACHMENT_FILEAREA,
212                'contextid' => $context->id, 'itemid' => 0,
213                'filename' => 'introattachment.txt', 'filepath' => '/');
214        $fs = get_file_storage();
215        $fs->create_file_from_string($filerecord, 'Test intro attachment file');
216
217        $result = mod_assign_external::get_assignments();
218
219        // We need to execute the return values cleaning process to simulate the web service server.
220        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
221
222        // Check the course and assignment are returned.
223        $this->assertEquals(1, count($result['courses']));
224        $course = $result['courses'][0];
225        $this->assertEquals('Lightwork Course 1', $course['fullname']);
226        $this->assertEquals('Lightwork Course 1', $course['shortname']);
227        $this->assertEquals(1, count($course['assignments']));
228        $assignment = $course['assignments'][0];
229        $this->assertEquals($assign1->id, $assignment['id']);
230        $this->assertEquals($course1->id, $assignment['course']);
231        $this->assertEquals('English', $assignment['name']);
232        $this->assertStringContainsString('the assignment intro text here', $assignment['intro']);
233        $this->assertNotEmpty($assignment['configs']);
234        // Check the url of the file attatched.
235        $this->assertMatchesRegularExpression(
236            '@"' . $CFG->wwwroot . '/webservice/pluginfile.php/\d+/mod_assign/intro/intro\.txt"@', $assignment['intro']);
237        $this->assertEquals(1, $assignment['markingworkflow']);
238        $this->assertEquals(1, $assignment['markingallocation']);
239        $this->assertEquals(0, $assignment['preventsubmissionnotingroup']);
240
241        $this->assertCount(1, $assignment['introattachments']);
242        $this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);
243
244        // Now, hide the descritption until the submission from date.
245        $DB->set_field('assign', 'alwaysshowdescription', 0, array('id' => $assign1->id));
246        $DB->set_field('assign', 'allowsubmissionsfromdate', time() + DAYSECS, array('id' => $assign1->id));
247
248        $result = mod_assign_external::get_assignments(array($course1->id));
249
250        // We need to execute the return values cleaning process to simulate the web service server.
251        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
252
253        $this->assertEquals(1, count($result['courses']));
254        $course = $result['courses'][0];
255        $this->assertEquals('Lightwork Course 1', $course['fullname']);
256        $this->assertEquals(1, count($course['assignments']));
257        $assignment = $course['assignments'][0];
258        $this->assertEquals($assign1->id, $assignment['id']);
259        $this->assertEquals($course1->id, $assignment['course']);
260        $this->assertEquals('English', $assignment['name']);
261        $this->assertArrayNotHasKey('intro', $assignment);
262        $this->assertArrayNotHasKey('introattachments', $assignment);
263        $this->assertEquals(1, $assignment['markingworkflow']);
264        $this->assertEquals(1, $assignment['markingallocation']);
265        $this->assertEquals(0, $assignment['preventsubmissionnotingroup']);
266
267        $result = mod_assign_external::get_assignments(array($course2->id));
268
269        // We need to execute the return values cleaning process to simulate the web service server.
270        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
271
272        $this->assertEquals(0, count($result['courses']));
273        $this->assertEquals(1, count($result['warnings']));
274
275        // Test with non-enrolled user, but with view capabilities.
276        $this->setAdminUser();
277        $result = mod_assign_external::get_assignments();
278        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
279        $this->assertEquals(0, count($result['courses']));
280        $this->assertEquals(0, count($result['warnings']));
281
282        // Expect no courses, because we are not using the special flag.
283        $result = mod_assign_external::get_assignments(array($course1->id));
284        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
285        $this->assertCount(0, $result['courses']);
286
287        // Now use the special flag to return courses where you are not enroled in.
288        $result = mod_assign_external::get_assignments(array($course1->id), array(), true);
289        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
290        $this->assertCount(1, $result['courses']);
291
292        $course = $result['courses'][0];
293        $this->assertEquals('Lightwork Course 1', $course['fullname']);
294        $this->assertEquals(1, count($course['assignments']));
295        $assignment = $course['assignments'][0];
296        $this->assertEquals($assign1->id, $assignment['id']);
297        $this->assertEquals($course1->id, $assignment['course']);
298        $this->assertEquals('English', $assignment['name']);
299        $this->assertArrayNotHasKey('intro', $assignment);
300        $this->assertArrayNotHasKey('introattachments', $assignment);
301        $this->assertEquals(1, $assignment['markingworkflow']);
302        $this->assertEquals(1, $assignment['markingallocation']);
303        $this->assertEquals(0, $assignment['preventsubmissionnotingroup']);
304    }
305
306    /**
307     * Test get_assignments with submissionstatement.
308     */
309    public function test_get_assignments_with_submissionstatement() {
310        global $DB, $USER, $CFG;
311
312        $this->resetAfterTest(true);
313
314        // Setup test data. Create 2 assigns, one with requiresubmissionstatement and the other without it.
315        $course = $this->getDataGenerator()->create_course();
316        $assign = $this->getDataGenerator()->create_module('assign', array(
317            'course' => $course->id,
318            'requiresubmissionstatement' => 1
319        ));
320        $assign2 = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
321
322        // Create student.
323        $student = self::getDataGenerator()->create_user();
324
325        // Users enrolments.
326        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
327        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
328
329        // Update the submissionstatement.
330        $submissionstatement = 'This is a fake submission statement.';
331        set_config('submissionstatement', $submissionstatement, 'assign');
332
333        $this->setUser($student);
334
335        $result = mod_assign_external::get_assignments();
336        // We need to execute the return values cleaning process to simulate the web service server.
337        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
338
339        // Check that the amount of courses and assignments is right.
340        $this->assertCount(1, $result['courses']);
341        $assignmentsret = $result['courses'][0]['assignments'];
342        $this->assertCount(2, $assignmentsret);
343
344        // Order the returned assignments by ID.
345        usort($assignmentsret, function($a, $b) {
346            return strcmp($a['id'], $b['id']);
347        });
348
349        // Check that the first assign contains the submission statement.
350        $assignmentret = $assignmentsret[0];
351        $this->assertEquals($assign->id, $assignmentret['id']);
352        $this->assertEquals(1, $assignmentret['requiresubmissionstatement']);
353        $this->assertEquals($submissionstatement, $assignmentret['submissionstatement']);
354
355        // Check that the second assign does NOT contain the submission statement.
356        $assignmentret = $assignmentsret[1];
357        $this->assertEquals($assign2->id, $assignmentret['id']);
358        $this->assertEquals(0, $assignmentret['requiresubmissionstatement']);
359        $this->assertArrayNotHasKey('submissionstatement', $assignmentret);
360    }
361
362    /**
363     * Test get_submissions
364     */
365    public function test_get_submissions() {
366        global $DB, $USER;
367
368        $this->resetAfterTest(true);
369        // Create a course and assignment.
370        $coursedata['idnumber'] = 'idnumbercourse1';
371        $coursedata['fullname'] = 'Lightwork Course 1';
372        $coursedata['summary'] = 'Lightwork Course 1 description';
373        $coursedata['summaryformat'] = FORMAT_MOODLE;
374        $course1 = self::getDataGenerator()->create_course($coursedata);
375
376        $assigndata['course'] = $course1->id;
377        $assigndata['name'] = 'lightwork assignment';
378
379        $assign1 = self::getDataGenerator()->create_module('assign', $assigndata);
380
381        // Create a student with an online text submission.
382        // First attempt.
383        $student = self::getDataGenerator()->create_user();
384        $teacher = self::getDataGenerator()->create_user();
385        $submission = new stdClass();
386        $submission->assignment = $assign1->id;
387        $submission->userid = $student->id;
388        $submission->timecreated = time();
389        $submission->timemodified = $submission->timecreated;
390        $submission->status = 'draft';
391        $submission->attemptnumber = 0;
392        $submission->latest = 0;
393        $sid = $DB->insert_record('assign_submission', $submission);
394
395        // Second attempt.
396        $submission = new stdClass();
397        $submission->assignment = $assign1->id;
398        $submission->userid = $student->id;
399        $submission->timecreated = time();
400        $submission->timemodified = $submission->timecreated;
401        $submission->status = 'submitted';
402        $submission->attemptnumber = 1;
403        $submission->latest = 1;
404        $sid = $DB->insert_record('assign_submission', $submission);
405        $submission->id = $sid;
406
407        $onlinetextsubmission = new stdClass();
408        $onlinetextsubmission->onlinetext = "<p>online test text</p>";
409        $onlinetextsubmission->onlineformat = 1;
410        $onlinetextsubmission->submission = $submission->id;
411        $onlinetextsubmission->assignment = $assign1->id;
412        $DB->insert_record('assignsubmission_onlinetext', $onlinetextsubmission);
413
414        // Enrol the teacher in the course.
415        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
416        $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id);
417        $this->setUser($teacher);
418
419        $assignmentids[] = $assign1->id;
420        $result = mod_assign_external::get_submissions($assignmentids);
421        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
422
423        // Check the online text submission is NOT returned because the student is not yet enrolled in the course.
424        $this->assertEquals(1, count($result['assignments']));
425        $assignment = $result['assignments'][0];
426        $this->assertEquals($assign1->id, $assignment['assignmentid']);
427        $this->assertEquals(0, count($assignment['submissions']));
428
429        // Enrol the student in the course.
430        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
431        $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id);
432
433        $result = mod_assign_external::get_submissions($assignmentids);
434        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
435
436        $this->assertEquals(1, count($result['assignments']));
437        $assignment = $result['assignments'][0];
438        $this->assertEquals($assign1->id, $assignment['assignmentid']);
439        // Now, we get the submission because the user is enrolled.
440        $this->assertEquals(1, count($assignment['submissions']));
441        $submission = $assignment['submissions'][0];
442        $this->assertEquals($sid, $submission['id']);
443        $this->assertCount(1, $submission['plugins']);
444        $this->assertEquals('notgraded', $submission['gradingstatus']);
445
446        // Test locking the context.
447        set_config('contextlocking', 1);
448        $context = context_course::instance($course1->id);
449        $context->set_locked(true);
450
451        $this->setUser($teacher);
452        $assignmentids[] = $assign1->id;
453        $result = mod_assign_external::get_submissions($assignmentids);
454        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
455        $this->assertEquals(1, count($result['assignments']));
456    }
457
458    /**
459     * Test get_submissions with teamsubmission enabled
460     */
461    public function test_get_submissions_group_submission() {
462        global $DB;
463
464        $this->resetAfterTest(true);
465
466        $result = $this->create_assign_with_student_and_teacher(array(
467            'assignsubmission_onlinetext_enabled' => 1,
468            'teamsubmission' => 1
469        ));
470        $assignmodule = $result['assign'];
471        $student = $result['student'];
472        $teacher = $result['teacher'];
473        $course = $result['course'];
474        $context = context_course::instance($course->id);
475        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
476        $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
477        $cm = get_coursemodule_from_instance('assign', $assignmodule->id);
478        $context = context_module::instance($cm->id);
479        $assign = new mod_assign_testable_assign($context, $cm, $course);
480
481        groups_add_member($group, $student);
482
483        $this->setUser($student);
484        $submission = $assign->get_group_submission($student->id, $group->id, true);
485        $sid = $submission->id;
486
487        $this->setUser($teacher);
488
489        $assignmentids[] = $assignmodule->id;
490        $result = mod_assign_external::get_submissions($assignmentids);
491        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
492
493        $this->assertEquals(1, count($result['assignments']));
494        $assignment = $result['assignments'][0];
495        $this->assertEquals($assignmodule->id, $assignment['assignmentid']);
496        $this->assertEquals(1, count($assignment['submissions']));
497        $submission = $assignment['submissions'][0];
498        $this->assertEquals($sid, $submission['id']);
499        $this->assertEquals($group->id, $submission['groupid']);
500        $this->assertEquals(0, $submission['userid']);
501    }
502
503    /**
504     * Test get_submissions with teamsubmission enabled
505     * and a group having a higher attemptnumber than another
506     */
507    public function test_get_submissions_group_submission_attemptnumber() {
508        global $DB;
509        $this->resetAfterTest(true);
510
511        $result = $this->create_assign_with_student_and_teacher([
512            'assignsubmission_onlinetext_enabled' => 1,
513            'attemptreopenmethod' => 'manual',
514            'teamsubmission' => 1,
515        ]);
516        $assignmodule = $result['assign'];
517        $course = $result['course'];
518
519        $teacher = $result['teacher'];
520        $student1 = $result['student'];
521        $student2 = self::getDataGenerator()->create_user();
522
523        // Enrol second user into the course.
524        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
525        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
526
527        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
528        groups_add_member($group1, $student1);
529        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
530        groups_add_member($group2, $student2);
531
532        $this->setUser($student1);
533        mod_assign_external::save_submission(
534            $assignmodule->id,
535            [
536                'onlinetext_editor' => [
537                    'text' => 'Group 1, Submission 1',
538                    'format' => FORMAT_PLAIN,
539                    'itemid' => file_get_unused_draft_itemid(),
540                ]
541            ]
542        );
543        $this->setUser($student2);
544        mod_assign_external::save_submission(
545            $assignmodule->id,
546            [
547                'onlinetext_editor' => [
548                    'text' => 'Group 2, Submission 1',
549                    'format' => FORMAT_PLAIN,
550                    'itemid' => file_get_unused_draft_itemid(),
551                ]
552            ]
553        );
554        mod_assign_external::submit_for_grading($assignmodule->id, 1);
555        $this->setUser($teacher);
556        mod_assign_external::save_grade($assignmodule->id, $student2->id, 0, -1, 1, "", 1);
557        $this->setUser($student2);
558        mod_assign_external::save_submission(
559            $assignmodule->id,
560            [
561                'onlinetext_editor' => [
562                    'text' => 'Group 2, Submission 2',
563                    'format' => FORMAT_PLAIN,
564                    'itemid' => file_get_unused_draft_itemid(),
565                ]
566            ]
567        );
568
569        $this->setUser($teacher);
570        $result = mod_assign_external::get_submissions([$assignmodule->id]);
571        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
572
573        $this->assertEquals(1, count($result['assignments']));
574        [$assignment] = $result['assignments'];
575        $this->assertEquals($assignmodule->id, $assignment['assignmentid']);
576
577        $this->assertEquals(2, count($assignment['submissions']));
578        $expectedsubmissions = ['Group 1, Submission 1', 'Group 2, Submission 2'];
579        foreach ($assignment['submissions'] as $submission) {
580            $this->assertContains($submission['plugins'][0]['editorfields'][0]['text'], $expectedsubmissions);
581        }
582    }
583
584    /**
585     * Test get_user_flags
586     */
587    public function test_get_user_flags() {
588        global $DB, $USER;
589
590        $this->resetAfterTest(true);
591        // Create a course and assignment.
592        $coursedata['idnumber'] = 'idnumbercourse';
593        $coursedata['fullname'] = 'Lightwork Course';
594        $coursedata['summary'] = 'Lightwork Course description';
595        $coursedata['summaryformat'] = FORMAT_MOODLE;
596        $course = self::getDataGenerator()->create_course($coursedata);
597
598        $assigndata['course'] = $course->id;
599        $assigndata['name'] = 'lightwork assignment';
600
601        $assign = self::getDataGenerator()->create_module('assign', $assigndata);
602
603        // Create a manual enrolment record.
604        $manualenroldata['enrol'] = 'manual';
605        $manualenroldata['status'] = 0;
606        $manualenroldata['courseid'] = $course->id;
607        $enrolid = $DB->insert_record('enrol', $manualenroldata);
608
609        // Create a teacher and give them capabilities.
610        $context = context_course::instance($course->id);
611        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
612        $context = context_module::instance($assign->cmid);
613        $this->assignUserCapability('mod/assign:grade', $context->id, $roleid);
614
615        // Create the teacher's enrolment record.
616        $userenrolmentdata['status'] = 0;
617        $userenrolmentdata['enrolid'] = $enrolid;
618        $userenrolmentdata['userid'] = $USER->id;
619        $DB->insert_record('user_enrolments', $userenrolmentdata);
620
621        // Create a student and give them a user flag record.
622        $student = self::getDataGenerator()->create_user();
623        $userflag = new stdClass();
624        $userflag->assignment = $assign->id;
625        $userflag->userid = $student->id;
626        $userflag->locked = 0;
627        $userflag->mailed = 0;
628        $userflag->extensionduedate = 0;
629        $userflag->workflowstate = 'inmarking';
630        $userflag->allocatedmarker = $USER->id;
631
632        $DB->insert_record('assign_user_flags', $userflag);
633
634        $assignmentids[] = $assign->id;
635        $result = mod_assign_external::get_user_flags($assignmentids);
636
637        // We need to execute the return values cleaning process to simulate the web service server.
638        $result = external_api::clean_returnvalue(mod_assign_external::get_user_flags_returns(), $result);
639
640        // Check that the correct user flag information for the student is returned.
641        $this->assertEquals(1, count($result['assignments']));
642        $assignment = $result['assignments'][0];
643        $this->assertEquals($assign->id, $assignment['assignmentid']);
644        // Should be one user flag record.
645        $this->assertEquals(1, count($assignment['userflags']));
646        $userflag = $assignment['userflags'][0];
647        $this->assertEquals($student->id, $userflag['userid']);
648        $this->assertEquals(0, $userflag['locked']);
649        $this->assertEquals(0, $userflag['mailed']);
650        $this->assertEquals(0, $userflag['extensionduedate']);
651        $this->assertEquals('inmarking', $userflag['workflowstate']);
652        $this->assertEquals($USER->id, $userflag['allocatedmarker']);
653    }
654
655    /**
656     * Test get_user_mappings
657     */
658    public function test_get_user_mappings() {
659        global $DB, $USER;
660
661        $this->resetAfterTest(true);
662        // Create a course and assignment.
663        $coursedata['idnumber'] = 'idnumbercourse';
664        $coursedata['fullname'] = 'Lightwork Course';
665        $coursedata['summary'] = 'Lightwork Course description';
666        $coursedata['summaryformat'] = FORMAT_MOODLE;
667        $course = self::getDataGenerator()->create_course($coursedata);
668
669        $assigndata['course'] = $course->id;
670        $assigndata['name'] = 'lightwork assignment';
671
672        $assign = self::getDataGenerator()->create_module('assign', $assigndata);
673
674        // Create a manual enrolment record.
675        $manualenroldata['enrol'] = 'manual';
676        $manualenroldata['status'] = 0;
677        $manualenroldata['courseid'] = $course->id;
678        $enrolid = $DB->insert_record('enrol', $manualenroldata);
679
680        // Create a teacher and give them capabilities.
681        $context = context_course::instance($course->id);
682        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
683        $context = context_module::instance($assign->cmid);
684        $this->assignUserCapability('mod/assign:revealidentities', $context->id, $roleid);
685
686        // Create the teacher's enrolment record.
687        $userenrolmentdata['status'] = 0;
688        $userenrolmentdata['enrolid'] = $enrolid;
689        $userenrolmentdata['userid'] = $USER->id;
690        $DB->insert_record('user_enrolments', $userenrolmentdata);
691
692        // Create a student and give them a user mapping record.
693        $student = self::getDataGenerator()->create_user();
694        $mapping = new stdClass();
695        $mapping->assignment = $assign->id;
696        $mapping->userid = $student->id;
697
698        $DB->insert_record('assign_user_mapping', $mapping);
699
700        $assignmentids[] = $assign->id;
701        $result = mod_assign_external::get_user_mappings($assignmentids);
702
703        // We need to execute the return values cleaning process to simulate the web service server.
704        $result = external_api::clean_returnvalue(mod_assign_external::get_user_mappings_returns(), $result);
705
706        // Check that the correct user mapping information for the student is returned.
707        $this->assertEquals(1, count($result['assignments']));
708        $assignment = $result['assignments'][0];
709        $this->assertEquals($assign->id, $assignment['assignmentid']);
710        // Should be one user mapping record.
711        $this->assertEquals(1, count($assignment['mappings']));
712        $mapping = $assignment['mappings'][0];
713        $this->assertEquals($student->id, $mapping['userid']);
714    }
715
716    /**
717     * Test lock_submissions
718     */
719    public function test_lock_submissions() {
720        global $DB, $USER;
721
722        $this->resetAfterTest(true);
723        // Create a course and assignment and users.
724        $course = self::getDataGenerator()->create_course();
725
726        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
727        $params['course'] = $course->id;
728        $params['assignsubmission_onlinetext_enabled'] = 1;
729        $instance = $generator->create_instance($params);
730        $cm = get_coursemodule_from_instance('assign', $instance->id);
731        $context = context_module::instance($cm->id);
732
733        $assign = new assign($context, $cm, $course);
734
735        $student1 = self::getDataGenerator()->create_user();
736        $student2 = self::getDataGenerator()->create_user();
737        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
738        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
739        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
740        $teacher = self::getDataGenerator()->create_user();
741        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
742        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
743
744        // Create a student1 with an online text submission.
745        // Simulate a submission.
746        $this->setUser($student1);
747        $submission = $assign->get_user_submission($student1->id, true);
748        $data = new stdClass();
749        $data->onlinetext_editor = array(
750            'itemid' => file_get_unused_draft_itemid(),
751            'text' => 'Submission text',
752            'format' => FORMAT_MOODLE);
753        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
754        $plugin->save($submission, $data);
755
756        // Ready to test.
757        $this->setUser($teacher);
758        $students = array($student1->id, $student2->id);
759        $result = mod_assign_external::lock_submissions($instance->id, $students);
760        $result = external_api::clean_returnvalue(mod_assign_external::lock_submissions_returns(), $result);
761
762        // Check for 0 warnings.
763        $this->assertEquals(0, count($result));
764
765        $this->setUser($student2);
766        $submission = $assign->get_user_submission($student2->id, true);
767        $data = new stdClass();
768        $data->onlinetext_editor = array(
769            'itemid' => file_get_unused_draft_itemid(),
770            'text' => 'Submission text',
771            'format' => FORMAT_MOODLE);
772        $notices = array();
773        $this->expectException(moodle_exception::class);
774        $assign->save_submission($data, $notices);
775    }
776
777    /**
778     * Test unlock_submissions
779     */
780    public function test_unlock_submissions() {
781        global $DB, $USER;
782
783        $this->resetAfterTest(true);
784        // Create a course and assignment and users.
785        $course = self::getDataGenerator()->create_course();
786
787        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
788        $params['course'] = $course->id;
789        $params['assignsubmission_onlinetext_enabled'] = 1;
790        $instance = $generator->create_instance($params);
791        $cm = get_coursemodule_from_instance('assign', $instance->id);
792        $context = context_module::instance($cm->id);
793
794        $assign = new assign($context, $cm, $course);
795
796        $student1 = self::getDataGenerator()->create_user();
797        $student2 = self::getDataGenerator()->create_user();
798        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
799        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
800        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
801        $teacher = self::getDataGenerator()->create_user();
802        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
803        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
804
805        // Create a student1 with an online text submission.
806        // Simulate a submission.
807        $this->setUser($student1);
808        $submission = $assign->get_user_submission($student1->id, true);
809        $data = new stdClass();
810        $data->onlinetext_editor = array(
811            'itemid' => file_get_unused_draft_itemid(),
812            'text' => 'Submission text',
813            'format' => FORMAT_MOODLE);
814        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
815        $plugin->save($submission, $data);
816
817        // Ready to test.
818        $this->setUser($teacher);
819        $students = array($student1->id, $student2->id);
820        $result = mod_assign_external::lock_submissions($instance->id, $students);
821        $result = external_api::clean_returnvalue(mod_assign_external::lock_submissions_returns(), $result);
822
823        // Check for 0 warnings.
824        $this->assertEquals(0, count($result));
825
826        $result = mod_assign_external::unlock_submissions($instance->id, $students);
827        $result = external_api::clean_returnvalue(mod_assign_external::unlock_submissions_returns(), $result);
828
829        // Check for 0 warnings.
830        $this->assertEquals(0, count($result));
831
832        $this->setUser($student2);
833        $submission = $assign->get_user_submission($student2->id, true);
834        $data = new stdClass();
835        $data->onlinetext_editor = array(
836            'itemid' => file_get_unused_draft_itemid(),
837            'text' => 'Submission text',
838            'format' => FORMAT_MOODLE);
839        $notices = array();
840        $assign->save_submission($data, $notices);
841    }
842
843    /**
844     * Test submit_for_grading
845     */
846    public function test_submit_for_grading() {
847        global $DB, $USER;
848
849        $this->resetAfterTest(true);
850        // Create a course and assignment and users.
851        $course = self::getDataGenerator()->create_course();
852
853        set_config('submissionreceipts', 0, 'assign');
854        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
855        $params['course'] = $course->id;
856        $params['assignsubmission_onlinetext_enabled'] = 1;
857        $params['submissiondrafts'] = 1;
858        $params['sendnotifications'] = 0;
859        $params['requiresubmissionstatement'] = 1;
860        $instance = $generator->create_instance($params);
861        $cm = get_coursemodule_from_instance('assign', $instance->id);
862        $context = context_module::instance($cm->id);
863
864        $assign = new assign($context, $cm, $course);
865
866        $student1 = self::getDataGenerator()->create_user();
867        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
868        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
869
870        // Create a student1 with an online text submission.
871        // Simulate a submission.
872        $this->setUser($student1);
873        $submission = $assign->get_user_submission($student1->id, true);
874        $data = new stdClass();
875        $data->onlinetext_editor = array(
876            'itemid' => file_get_unused_draft_itemid(),
877            'text' => 'Submission text',
878            'format' => FORMAT_MOODLE);
879        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
880        $plugin->save($submission, $data);
881
882        $result = mod_assign_external::submit_for_grading($instance->id, false);
883        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
884
885        // Should be 1 fail because the submission statement was not aceptted.
886        $this->assertEquals(1, count($result));
887
888        $result = mod_assign_external::submit_for_grading($instance->id, true);
889        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
890
891        // Check for 0 warnings.
892        $this->assertEquals(0, count($result));
893
894        $submission = $assign->get_user_submission($student1->id, false);
895
896        $this->assertEquals(ASSIGN_SUBMISSION_STATUS_SUBMITTED, $submission->status);
897    }
898
899    /**
900     * Test save_user_extensions
901     */
902    public function test_save_user_extensions() {
903        global $DB, $USER;
904
905        $this->resetAfterTest(true);
906        // Create a course and assignment and users.
907        $course = self::getDataGenerator()->create_course();
908
909        $teacher = self::getDataGenerator()->create_user();
910        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
911        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
912        $this->setUser($teacher);
913
914        $now = time();
915        $yesterday = $now - 24 * 60 * 60;
916        $tomorrow = $now + 24 * 60 * 60;
917        set_config('submissionreceipts', 0, 'assign');
918        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
919        $params['course'] = $course->id;
920        $params['submissiondrafts'] = 1;
921        $params['sendnotifications'] = 0;
922        $params['duedate'] = $yesterday;
923        $params['cutoffdate'] = $now - 10;
924        $instance = $generator->create_instance($params);
925        $cm = get_coursemodule_from_instance('assign', $instance->id);
926        $context = context_module::instance($cm->id);
927
928        $assign = new assign($context, $cm, $course);
929
930        $student1 = self::getDataGenerator()->create_user();
931        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
932        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
933
934        $this->setUser($student1);
935        $result = mod_assign_external::submit_for_grading($instance->id, true);
936        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
937
938        // Check for 0 warnings.
939        $this->assertEquals(1, count($result));
940
941        $this->setUser($teacher);
942        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));
943        $result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);
944        $this->assertEquals(1, count($result));
945
946        $this->setUser($teacher);
947        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($yesterday - 10));
948        $result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);
949        $this->assertEquals(1, count($result));
950
951        $this->setUser($teacher);
952        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($tomorrow));
953        $result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);
954        $this->assertEquals(0, count($result));
955
956        $this->setUser($student1);
957        $result = mod_assign_external::submit_for_grading($instance->id, true);
958        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
959        $this->assertEquals(0, count($result));
960
961        $this->setUser($student1);
962        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));
963        $result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);
964    }
965
966    /**
967     * Test reveal_identities
968     */
969    public function test_reveal_identities() {
970        global $DB, $USER;
971
972        $this->resetAfterTest(true);
973        // Create a course and assignment and users.
974        $course = self::getDataGenerator()->create_course();
975
976        $teacher = self::getDataGenerator()->create_user();
977        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
978        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
979        $this->setUser($teacher);
980
981        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
982        $params['course'] = $course->id;
983        $params['submissiondrafts'] = 1;
984        $params['sendnotifications'] = 0;
985        $params['blindmarking'] = 1;
986        $instance = $generator->create_instance($params);
987        $cm = get_coursemodule_from_instance('assign', $instance->id);
988        $context = context_module::instance($cm->id);
989
990        $assign = new assign($context, $cm, $course);
991
992        $student1 = self::getDataGenerator()->create_user();
993        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
994        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
995
996        $this->setUser($student1);
997        $this->expectException(required_capability_exception::class);
998        $result = mod_assign_external::reveal_identities($instance->id);
999        $result = external_api::clean_returnvalue(mod_assign_external::reveal_identities_returns(), $result);
1000        $this->assertEquals(1, count($result));
1001        $this->assertEquals(true, $assign->is_blind_marking());
1002
1003        $this->setUser($teacher);
1004        $result = mod_assign_external::reveal_identities($instance->id);
1005        $result = external_api::clean_returnvalue(mod_assign_external::reveal_identities_returns(), $result);
1006        $this->assertEquals(0, count($result));
1007        $this->assertEquals(false, $assign->is_blind_marking());
1008
1009        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1010        $params['course'] = $course->id;
1011        $params['submissiondrafts'] = 1;
1012        $params['sendnotifications'] = 0;
1013        $params['blindmarking'] = 0;
1014        $instance = $generator->create_instance($params);
1015        $cm = get_coursemodule_from_instance('assign', $instance->id);
1016        $context = context_module::instance($cm->id);
1017
1018        $assign = new assign($context, $cm, $course);
1019        $result = mod_assign_external::reveal_identities($instance->id);
1020        $result = external_api::clean_returnvalue(mod_assign_external::reveal_identities_returns(), $result);
1021        $this->assertEquals(1, count($result));
1022        $this->assertEquals(false, $assign->is_blind_marking());
1023
1024    }
1025
1026    /**
1027     * Test revert_submissions_to_draft
1028     */
1029    public function test_revert_submissions_to_draft() {
1030        global $DB, $USER;
1031
1032        $this->resetAfterTest(true);
1033        set_config('submissionreceipts', 0, 'assign');
1034        // Create a course and assignment and users.
1035        $course = self::getDataGenerator()->create_course();
1036
1037        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1038        $params['course'] = $course->id;
1039        $params['sendnotifications'] = 0;
1040        $params['submissiondrafts'] = 1;
1041        $instance = $generator->create_instance($params);
1042        $cm = get_coursemodule_from_instance('assign', $instance->id);
1043        $context = context_module::instance($cm->id);
1044
1045        $assign = new assign($context, $cm, $course);
1046
1047        $student1 = self::getDataGenerator()->create_user();
1048        $student2 = self::getDataGenerator()->create_user();
1049        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1050        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1051        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
1052        $teacher = self::getDataGenerator()->create_user();
1053        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1054        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1055
1056        // Create a student1 with an online text submission.
1057        // Simulate a submission.
1058        $this->setUser($student1);
1059        $result = mod_assign_external::submit_for_grading($instance->id, true);
1060        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
1061        $this->assertEquals(0, count($result));
1062
1063        // Ready to test.
1064        $this->setUser($teacher);
1065        $students = array($student1->id, $student2->id);
1066        $result = mod_assign_external::revert_submissions_to_draft($instance->id, array($student1->id));
1067        $result = external_api::clean_returnvalue(mod_assign_external::revert_submissions_to_draft_returns(), $result);
1068
1069        // Check for 0 warnings.
1070        $this->assertEquals(0, count($result));
1071    }
1072
1073    /**
1074     * Test save_submission
1075     */
1076    public function test_save_submission() {
1077        global $DB, $USER;
1078
1079        $this->resetAfterTest(true);
1080        // Create a course and assignment and users.
1081        $course = self::getDataGenerator()->create_course();
1082
1083        $teacher = self::getDataGenerator()->create_user();
1084        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1085        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1086        $this->setUser($teacher);
1087
1088        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1089        $params['course'] = $course->id;
1090        $params['assignsubmission_onlinetext_enabled'] = 1;
1091        $params['assignsubmission_file_enabled'] = 1;
1092        $params['assignsubmission_file_maxfiles'] = 5;
1093        $params['assignsubmission_file_maxsizebytes'] = 1024 * 1024;
1094        $instance = $generator->create_instance($params);
1095        $cm = get_coursemodule_from_instance('assign', $instance->id);
1096        $context = context_module::instance($cm->id);
1097
1098        $assign = new assign($context, $cm, $course);
1099
1100        $student1 = self::getDataGenerator()->create_user();
1101        $student2 = self::getDataGenerator()->create_user();
1102        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1103        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1104        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
1105        // Create a student1 with an online text submission.
1106        // Simulate a submission.
1107        $this->setUser($student1);
1108
1109        // Create a file in a draft area.
1110        $draftidfile = file_get_unused_draft_itemid();
1111
1112        $usercontext = context_user::instance($student1->id);
1113        $filerecord = array(
1114            'contextid' => $usercontext->id,
1115            'component' => 'user',
1116            'filearea'  => 'draft',
1117            'itemid'    => $draftidfile,
1118            'filepath'  => '/',
1119            'filename'  => 'testtext.txt',
1120        );
1121
1122        $fs = get_file_storage();
1123        $fs->create_file_from_string($filerecord, 'text contents');
1124
1125        // Create another file in a different draft area.
1126        $draftidonlinetext = file_get_unused_draft_itemid();
1127
1128        $filerecord = array(
1129            'contextid' => $usercontext->id,
1130            'component' => 'user',
1131            'filearea'  => 'draft',
1132            'itemid'    => $draftidonlinetext,
1133            'filepath'  => '/',
1134            'filename'  => 'shouldbeanimage.txt',
1135        );
1136
1137        $fs->create_file_from_string($filerecord, 'image contents (not really)');
1138
1139        // Now try a submission.
1140        $submissionpluginparams = array();
1141        $submissionpluginparams['files_filemanager'] = $draftidfile;
1142        $onlinetexteditorparams = array(
1143            'text' => '<p>Yeeha!</p>',
1144            'format' => 1,
1145            'itemid' => $draftidonlinetext);
1146        $submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;
1147        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
1148        $result = external_api::clean_returnvalue(mod_assign_external::save_submission_returns(), $result);
1149
1150        $this->assertEquals(0, count($result));
1151
1152        // Set up a due and cutoff passed date.
1153        $instance->duedate = time() - WEEKSECS;
1154        $instance->cutoffdate = time() - WEEKSECS;
1155        $DB->update_record('assign', $instance);
1156
1157        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
1158        $result = external_api::clean_returnvalue(mod_assign_external::save_submission_returns(), $result);
1159
1160        $this->assertCount(1, $result);
1161        $this->assertEquals(get_string('duedatereached', 'assign'), $result[0]['item']);
1162    }
1163
1164    /**
1165     * Test save_grade
1166     */
1167    public function test_save_grade() {
1168        global $DB, $USER;
1169
1170        $this->resetAfterTest(true);
1171        // Create a course and assignment and users.
1172        $course = self::getDataGenerator()->create_course();
1173
1174        $teacher = self::getDataGenerator()->create_user();
1175        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1176        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1177        $this->setUser($teacher);
1178
1179        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1180        $params['course'] = $course->id;
1181        $params['assignfeedback_file_enabled'] = 1;
1182        $params['assignfeedback_comments_enabled'] = 1;
1183        $instance = $generator->create_instance($params);
1184        $cm = get_coursemodule_from_instance('assign', $instance->id);
1185        $context = context_module::instance($cm->id);
1186
1187        $assign = new assign($context, $cm, $course);
1188
1189        $student1 = self::getDataGenerator()->create_user();
1190        $student2 = self::getDataGenerator()->create_user();
1191        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1192        $this->getDataGenerator()->enrol_user($student1->id,
1193                                              $course->id,
1194                                              $studentrole->id);
1195        $this->getDataGenerator()->enrol_user($student2->id,
1196                                              $course->id,
1197                                              $studentrole->id);
1198        // Simulate a grade.
1199        $this->setUser($teacher);
1200
1201        // Create a file in a draft area.
1202        $draftidfile = file_get_unused_draft_itemid();
1203
1204        $usercontext = context_user::instance($teacher->id);
1205        $filerecord = array(
1206            'contextid' => $usercontext->id,
1207            'component' => 'user',
1208            'filearea'  => 'draft',
1209            'itemid'    => $draftidfile,
1210            'filepath'  => '/',
1211            'filename'  => 'testtext.txt',
1212        );
1213
1214        $fs = get_file_storage();
1215        $fs->create_file_from_string($filerecord, 'text contents');
1216
1217        // Now try a grade.
1218        $feedbackpluginparams = array();
1219        $feedbackpluginparams['files_filemanager'] = $draftidfile;
1220        $feedbackeditorparams = array('text' => 'Yeeha!',
1221                                        'format' => 1);
1222        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
1223        $result = mod_assign_external::save_grade(
1224            $instance->id,
1225            $student1->id,
1226            50.0,
1227            -1,
1228            true,
1229            'released',
1230            false,
1231            $feedbackpluginparams);
1232        // No warnings.
1233        $this->assertNull($result);
1234
1235        $result = mod_assign_external::get_grades(array($instance->id));
1236        $result = external_api::clean_returnvalue(mod_assign_external::get_grades_returns(), $result);
1237
1238        $this->assertEquals((float)$result['assignments'][0]['grades'][0]['grade'], '50.0');
1239    }
1240
1241    /**
1242     * Test save grades with advanced grading data
1243     */
1244    public function test_save_grades_with_advanced_grading() {
1245        global $DB, $USER;
1246
1247        $this->resetAfterTest(true);
1248        // Create a course and assignment and users.
1249        $course = self::getDataGenerator()->create_course();
1250
1251        $teacher = self::getDataGenerator()->create_user();
1252        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1253        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1254
1255        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1256        $params['course'] = $course->id;
1257        $params['assignfeedback_file_enabled'] = 0;
1258        $params['assignfeedback_comments_enabled'] = 0;
1259        $instance = $generator->create_instance($params);
1260        $cm = get_coursemodule_from_instance('assign', $instance->id);
1261        $context = context_module::instance($cm->id);
1262
1263        $assign = new assign($context, $cm, $course);
1264
1265        $student1 = self::getDataGenerator()->create_user();
1266        $student2 = self::getDataGenerator()->create_user();
1267        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1268        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1269        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
1270
1271        $this->setUser($teacher);
1272
1273        $feedbackpluginparams = array();
1274        $feedbackpluginparams['files_filemanager'] = 0;
1275        $feedbackeditorparams = array('text' => '', 'format' => 1);
1276        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
1277
1278        // Create advanced grading data.
1279        // Create grading area.
1280        $gradingarea = array(
1281            'contextid' => $context->id,
1282            'component' => 'mod_assign',
1283            'areaname' => 'submissions',
1284            'activemethod' => 'rubric'
1285        );
1286        $areaid = $DB->insert_record('grading_areas', $gradingarea);
1287
1288        // Create a rubric grading definition.
1289        $rubricdefinition = array (
1290            'areaid' => $areaid,
1291            'method' => 'rubric',
1292            'name' => 'test',
1293            'status' => 20,
1294            'copiedfromid' => 1,
1295            'timecreated' => 1,
1296            'usercreated' => $teacher->id,
1297            'timemodified' => 1,
1298            'usermodified' => $teacher->id,
1299            'timecopied' => 0
1300        );
1301        $definitionid = $DB->insert_record('grading_definitions', $rubricdefinition);
1302
1303        // Create a criterion with a level.
1304        $rubriccriteria = array (
1305             'definitionid' => $definitionid,
1306             'sortorder' => 1,
1307             'description' => 'Demonstrate an understanding of disease control',
1308             'descriptionformat' => 0
1309        );
1310        $criterionid = $DB->insert_record('gradingform_rubric_criteria', $rubriccriteria);
1311        $rubriclevel1 = array (
1312            'criterionid' => $criterionid,
1313            'score' => 50,
1314            'definition' => 'pass',
1315            'definitionformat' => 0
1316        );
1317        $rubriclevel2 = array (
1318            'criterionid' => $criterionid,
1319            'score' => 100,
1320            'definition' => 'excellent',
1321            'definitionformat' => 0
1322        );
1323        $rubriclevel3 = array (
1324            'criterionid' => $criterionid,
1325            'score' => 0,
1326            'definition' => 'fail',
1327            'definitionformat' => 0
1328        );
1329        $levelid1 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel1);
1330        $levelid2 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel2);
1331        $levelid3 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel3);
1332
1333        // Create the filling.
1334        $student1filling = array (
1335            'criterionid' => $criterionid,
1336            'levelid' => $levelid1,
1337            'remark' => 'well done you passed',
1338            'remarkformat' => 0
1339        );
1340
1341        $student2filling = array (
1342            'criterionid' => $criterionid,
1343            'levelid' => $levelid2,
1344            'remark' => 'Excellent work',
1345            'remarkformat' => 0
1346        );
1347
1348        $student1criteria = array(array('criterionid' => $criterionid, 'fillings' => array($student1filling)));
1349        $student1advancedgradingdata = array('rubric' => array('criteria' => $student1criteria));
1350
1351        $student2criteria = array(array('criterionid' => $criterionid, 'fillings' => array($student2filling)));
1352        $student2advancedgradingdata = array('rubric' => array('criteria' => $student2criteria));
1353
1354        $grades = array();
1355        $student1gradeinfo = array();
1356        $student1gradeinfo['userid'] = $student1->id;
1357        $student1gradeinfo['grade'] = 0; // Ignored since advanced grading is being used.
1358        $student1gradeinfo['attemptnumber'] = -1;
1359        $student1gradeinfo['addattempt'] = true;
1360        $student1gradeinfo['workflowstate'] = 'released';
1361        $student1gradeinfo['plugindata'] = $feedbackpluginparams;
1362        $student1gradeinfo['advancedgradingdata'] = $student1advancedgradingdata;
1363        $grades[] = $student1gradeinfo;
1364
1365        $student2gradeinfo = array();
1366        $student2gradeinfo['userid'] = $student2->id;
1367        $student2gradeinfo['grade'] = 0; // Ignored since advanced grading is being used.
1368        $student2gradeinfo['attemptnumber'] = -1;
1369        $student2gradeinfo['addattempt'] = true;
1370        $student2gradeinfo['workflowstate'] = 'released';
1371        $student2gradeinfo['plugindata'] = $feedbackpluginparams;
1372        $student2gradeinfo['advancedgradingdata'] = $student2advancedgradingdata;
1373        $grades[] = $student2gradeinfo;
1374
1375        $result = mod_assign_external::save_grades($instance->id, false, $grades);
1376        $this->assertNull($result);
1377
1378        $student1grade = $DB->get_record('assign_grades',
1379                                         array('userid' => $student1->id, 'assignment' => $instance->id),
1380                                         '*',
1381                                         MUST_EXIST);
1382        $this->assertEquals((float)$student1grade->grade, '50.0');
1383
1384        $student2grade = $DB->get_record('assign_grades',
1385                                         array('userid' => $student2->id, 'assignment' => $instance->id),
1386                                         '*',
1387                                         MUST_EXIST);
1388        $this->assertEquals((float)$student2grade->grade, '100.0');
1389    }
1390
1391    /**
1392     * Test save grades for a team submission
1393     */
1394    public function test_save_grades_with_group_submission() {
1395        global $DB, $USER, $CFG;
1396        require_once($CFG->dirroot . '/group/lib.php');
1397
1398        $this->resetAfterTest(true);
1399        // Create a course and assignment and users.
1400        $course = self::getDataGenerator()->create_course();
1401
1402        $teacher = self::getDataGenerator()->create_user();
1403        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1404        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1405
1406        $groupingdata = array();
1407        $groupingdata['courseid'] = $course->id;
1408        $groupingdata['name'] = 'Group assignment grouping';
1409
1410        $grouping = self::getDataGenerator()->create_grouping($groupingdata);
1411
1412        $group1data = array();
1413        $group1data['courseid'] = $course->id;
1414        $group1data['name'] = 'Team 1';
1415        $group2data = array();
1416        $group2data['courseid'] = $course->id;
1417        $group2data['name'] = 'Team 2';
1418
1419        $group1 = self::getDataGenerator()->create_group($group1data);
1420        $group2 = self::getDataGenerator()->create_group($group2data);
1421
1422        groups_assign_grouping($grouping->id, $group1->id);
1423        groups_assign_grouping($grouping->id, $group2->id);
1424
1425        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1426        $params['course'] = $course->id;
1427        $params['teamsubmission'] = 1;
1428        $params['teamsubmissiongroupingid'] = $grouping->id;
1429        $instance = $generator->create_instance($params);
1430        $cm = get_coursemodule_from_instance('assign', $instance->id);
1431        $context = context_module::instance($cm->id);
1432
1433        $assign = new assign($context, $cm, $course);
1434
1435        $student1 = self::getDataGenerator()->create_user();
1436        $student2 = self::getDataGenerator()->create_user();
1437        $student3 = self::getDataGenerator()->create_user();
1438        $student4 = self::getDataGenerator()->create_user();
1439        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1440        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1441        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
1442        $this->getDataGenerator()->enrol_user($student3->id, $course->id, $studentrole->id);
1443        $this->getDataGenerator()->enrol_user($student4->id, $course->id, $studentrole->id);
1444
1445        groups_add_member($group1->id, $student1->id);
1446        groups_add_member($group1->id, $student2->id);
1447        groups_add_member($group1->id, $student3->id);
1448        groups_add_member($group2->id, $student4->id);
1449        $this->setUser($teacher);
1450
1451        $feedbackpluginparams = array();
1452        $feedbackpluginparams['files_filemanager'] = 0;
1453        $feedbackeditorparams = array('text' => '', 'format' => 1);
1454        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
1455
1456        $grades1 = array();
1457        $student1gradeinfo = array();
1458        $student1gradeinfo['userid'] = $student1->id;
1459        $student1gradeinfo['grade'] = 50;
1460        $student1gradeinfo['attemptnumber'] = -1;
1461        $student1gradeinfo['addattempt'] = true;
1462        $student1gradeinfo['workflowstate'] = 'released';
1463        $student1gradeinfo['plugindata'] = $feedbackpluginparams;
1464        $grades1[] = $student1gradeinfo;
1465
1466        $student2gradeinfo = array();
1467        $student2gradeinfo['userid'] = $student2->id;
1468        $student2gradeinfo['grade'] = 75;
1469        $student2gradeinfo['attemptnumber'] = -1;
1470        $student2gradeinfo['addattempt'] = true;
1471        $student2gradeinfo['workflowstate'] = 'released';
1472        $student2gradeinfo['plugindata'] = $feedbackpluginparams;
1473        $grades1[] = $student2gradeinfo;
1474
1475        // Expect an exception since 2 grades have been submitted for the same team.
1476        $this->expectException(invalid_parameter_exception::class);
1477        $result = mod_assign_external::save_grades($instance->id, true, $grades1);
1478        $result = external_api::clean_returnvalue(mod_assign_external::save_grades_returns(), $result);
1479
1480        $grades2 = array();
1481        $student3gradeinfo = array();
1482        $student3gradeinfo['userid'] = $student3->id;
1483        $student3gradeinfo['grade'] = 50;
1484        $student3gradeinfo['attemptnumber'] = -1;
1485        $student3gradeinfo['addattempt'] = true;
1486        $student3gradeinfo['workflowstate'] = 'released';
1487        $student3gradeinfo['plugindata'] = $feedbackpluginparams;
1488        $grades2[] = $student3gradeinfo;
1489
1490        $student4gradeinfo = array();
1491        $student4gradeinfo['userid'] = $student4->id;
1492        $student4gradeinfo['grade'] = 75;
1493        $student4gradeinfo['attemptnumber'] = -1;
1494        $student4gradeinfo['addattempt'] = true;
1495        $student4gradeinfo['workflowstate'] = 'released';
1496        $student4gradeinfo['plugindata'] = $feedbackpluginparams;
1497        $grades2[] = $student4gradeinfo;
1498        $result = mod_assign_external::save_grades($instance->id, true, $grades2);
1499        $result = external_api::clean_returnvalue(mod_assign_external::save_grades_returns(), $result);
1500        // There should be no warnings.
1501        $this->assertEquals(0, count($result));
1502
1503        $student3grade = $DB->get_record('assign_grades',
1504            array('userid' => $student3->id, 'assignment' => $instance->id),
1505            '*',
1506            MUST_EXIST);
1507        $this->assertEquals($student3grade->grade, '50.0');
1508
1509        $student4grade = $DB->get_record('assign_grades',
1510            array('userid' => $student4->id, 'assignment' => $instance->id),
1511            '*',
1512            MUST_EXIST);
1513        $this->assertEquals($student4grade->grade, '75.0');
1514    }
1515
1516    /**
1517     * Test copy_previous_attempt
1518     */
1519    public function test_copy_previous_attempt() {
1520        global $DB, $USER;
1521
1522        $this->resetAfterTest(true);
1523        // Create a course and assignment and users.
1524        $course = self::getDataGenerator()->create_course();
1525
1526        $teacher = self::getDataGenerator()->create_user();
1527        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1528        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1529        $this->setUser($teacher);
1530
1531        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1532        $params['course'] = $course->id;
1533        $params['assignsubmission_onlinetext_enabled'] = 1;
1534        $params['assignsubmission_file_enabled'] = 0;
1535        $params['assignfeedback_file_enabled'] = 0;
1536        $params['attemptreopenmethod'] = 'manual';
1537        $params['maxattempts'] = 5;
1538        $instance = $generator->create_instance($params);
1539        $cm = get_coursemodule_from_instance('assign', $instance->id);
1540        $context = context_module::instance($cm->id);
1541
1542        $assign = new assign($context, $cm, $course);
1543
1544        $student1 = self::getDataGenerator()->create_user();
1545        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1546        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1547        // Now try a submission.
1548        $this->setUser($student1);
1549        $draftidonlinetext = file_get_unused_draft_itemid();
1550        $submissionpluginparams = array();
1551        $onlinetexteditorparams = array('text' => 'Yeeha!', 'format' => 1, 'itemid' => $draftidonlinetext);
1552        $submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;
1553        $submissionpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
1554        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
1555        $result = external_api::clean_returnvalue(mod_assign_external::save_submission_returns(), $result);
1556
1557        $this->setUser($teacher);
1558        // Add a grade and reopen the attempt.
1559        // Now try a grade.
1560        $feedbackpluginparams = array();
1561        $feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
1562        $feedbackeditorparams = array('text' => 'Yeeha!', 'format' => 1);
1563        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
1564        $result = mod_assign_external::save_grade(
1565            $instance->id,
1566            $student1->id,
1567            50.0,
1568            -1,
1569            true,
1570            'released',
1571            false,
1572            $feedbackpluginparams);
1573        $this->assertNull($result);
1574
1575        $this->setUser($student1);
1576        // Now copy the previous attempt.
1577        $result = mod_assign_external::copy_previous_attempt($instance->id);
1578        $result = external_api::clean_returnvalue(mod_assign_external::copy_previous_attempt_returns(), $result);
1579        // No warnings.
1580        $this->assertEquals(0, count($result));
1581
1582        $this->setUser($teacher);
1583        $result = mod_assign_external::get_submissions(array($instance->id));
1584        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
1585
1586        // Check we are now on the second attempt.
1587        $this->assertEquals($result['assignments'][0]['submissions'][0]['attemptnumber'], 1);
1588        // Check the plugins data is not empty.
1589        $this->assertNotEmpty($result['assignments'][0]['submissions'][0]['plugins']);
1590
1591    }
1592
1593    /**
1594     * Test set_user_flags
1595     */
1596    public function test_set_user_flags() {
1597        global $DB, $USER;
1598
1599        $this->resetAfterTest(true);
1600        // Create a course and assignment.
1601        $coursedata['idnumber'] = 'idnumbercourse';
1602        $coursedata['fullname'] = 'Lightwork Course';
1603        $coursedata['summary'] = 'Lightwork Course description';
1604        $coursedata['summaryformat'] = FORMAT_MOODLE;
1605        $course = self::getDataGenerator()->create_course($coursedata);
1606
1607        $assigndata['course'] = $course->id;
1608        $assigndata['name'] = 'lightwork assignment';
1609
1610        $assign = self::getDataGenerator()->create_module('assign', $assigndata);
1611
1612        // Create a manual enrolment record.
1613        $manualenroldata['enrol'] = 'manual';
1614        $manualenroldata['status'] = 0;
1615        $manualenroldata['courseid'] = $course->id;
1616        $enrolid = $DB->insert_record('enrol', $manualenroldata);
1617
1618        // Create a teacher and give them capabilities.
1619        $context = context_course::instance($course->id);
1620        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
1621        $context = context_module::instance($assign->cmid);
1622        $this->assignUserCapability('mod/assign:grade', $context->id, $roleid);
1623
1624        // Create the teacher's enrolment record.
1625        $userenrolmentdata['status'] = 0;
1626        $userenrolmentdata['enrolid'] = $enrolid;
1627        $userenrolmentdata['userid'] = $USER->id;
1628        $DB->insert_record('user_enrolments', $userenrolmentdata);
1629
1630        // Create a student.
1631        $student = self::getDataGenerator()->create_user();
1632
1633        // Create test user flags record.
1634        $userflags = array();
1635        $userflag['userid'] = $student->id;
1636        $userflag['workflowstate'] = 'inmarking';
1637        $userflag['allocatedmarker'] = $USER->id;
1638        $userflags = array($userflag);
1639
1640        $createduserflags = mod_assign_external::set_user_flags($assign->id, $userflags);
1641        // We need to execute the return values cleaning process to simulate the web service server.
1642        $createduserflags = external_api::clean_returnvalue(mod_assign_external::set_user_flags_returns(), $createduserflags);
1643
1644        $this->assertEquals($student->id, $createduserflags[0]['userid']);
1645        $createduserflag = $DB->get_record('assign_user_flags', array('id' => $createduserflags[0]['id']));
1646
1647        // Confirm that all data was inserted correctly.
1648        $this->assertEquals($student->id,  $createduserflag->userid);
1649        $this->assertEquals($assign->id, $createduserflag->assignment);
1650        $this->assertEquals(0, $createduserflag->locked);
1651        $this->assertEquals(2, $createduserflag->mailed);
1652        $this->assertEquals(0, $createduserflag->extensionduedate);
1653        $this->assertEquals('inmarking', $createduserflag->workflowstate);
1654        $this->assertEquals($USER->id, $createduserflag->allocatedmarker);
1655
1656        // Create update data.
1657        $userflags = array();
1658        $userflag['userid'] = $createduserflag->userid;
1659        $userflag['workflowstate'] = 'readyforreview';
1660        $userflags = array($userflag);
1661
1662        $updateduserflags = mod_assign_external::set_user_flags($assign->id, $userflags);
1663        // We need to execute the return values cleaning process to simulate the web service server.
1664        $updateduserflags = external_api::clean_returnvalue(mod_assign_external::set_user_flags_returns(), $updateduserflags);
1665
1666        $this->assertEquals($student->id, $updateduserflags[0]['userid']);
1667        $updateduserflag = $DB->get_record('assign_user_flags', array('id' => $updateduserflags[0]['id']));
1668
1669        // Confirm that all data was updated correctly.
1670        $this->assertEquals($student->id,  $updateduserflag->userid);
1671        $this->assertEquals($assign->id, $updateduserflag->assignment);
1672        $this->assertEquals(0, $updateduserflag->locked);
1673        $this->assertEquals(2, $updateduserflag->mailed);
1674        $this->assertEquals(0, $updateduserflag->extensionduedate);
1675        $this->assertEquals('readyforreview', $updateduserflag->workflowstate);
1676        $this->assertEquals($USER->id, $updateduserflag->allocatedmarker);
1677    }
1678
1679    /**
1680     * Test view_grading_table
1681     */
1682    public function test_view_grading_table_invalid_instance() {
1683        global $DB;
1684
1685        $this->resetAfterTest(true);
1686
1687        // Setup test data.
1688        $course = $this->getDataGenerator()->create_course();
1689        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1690        $context = context_module::instance($assign->cmid);
1691        $cm = get_coursemodule_from_instance('assign', $assign->id);
1692
1693        // Test invalid instance id.
1694        $this->expectException(dml_missing_record_exception::class);
1695        mod_assign_external::view_grading_table(0);
1696    }
1697
1698    /**
1699     * Test view_grading_table
1700     */
1701    public function test_view_grading_table_not_enrolled() {
1702        global $DB;
1703
1704        $this->resetAfterTest(true);
1705
1706        // Setup test data.
1707        $course = $this->getDataGenerator()->create_course();
1708        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1709        $context = context_module::instance($assign->cmid);
1710        $cm = get_coursemodule_from_instance('assign', $assign->id);
1711
1712        // Test not-enrolled user.
1713        $user = self::getDataGenerator()->create_user();
1714        $this->setUser($user);
1715
1716        $this->expectException(require_login_exception::class);
1717        mod_assign_external::view_grading_table($assign->id);
1718    }
1719
1720    /**
1721     * Test view_grading_table
1722     */
1723    public function test_view_grading_table_correct() {
1724        global $DB;
1725
1726        $this->resetAfterTest(true);
1727
1728        // Setup test data.
1729        $course = $this->getDataGenerator()->create_course();
1730        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1731        $context = context_module::instance($assign->cmid);
1732        $cm = get_coursemodule_from_instance('assign', $assign->id);
1733
1734        // Test user with full capabilities.
1735        $user = self::getDataGenerator()->create_user();
1736        $this->setUser($user);
1737        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1738        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
1739
1740        // Trigger and capture the event.
1741        $sink = $this->redirectEvents();
1742
1743        $result = mod_assign_external::view_grading_table($assign->id);
1744        $result = external_api::clean_returnvalue(mod_assign_external::view_grading_table_returns(), $result);
1745
1746        $events = $sink->get_events();
1747        $this->assertCount(1, $events);
1748        $event = array_shift($events);
1749
1750        // Checking that the event contains the expected values.
1751        $this->assertInstanceOf('\mod_assign\event\grading_table_viewed', $event);
1752        $this->assertEquals($context, $event->get_context());
1753        $moodleurl = new \moodle_url('/mod/assign/view.php', array('id' => $cm->id));
1754        $this->assertEquals($moodleurl, $event->get_url());
1755        $this->assertEventContextNotUsed($event);
1756        $this->assertNotEmpty($event->get_name());
1757    }
1758
1759    /**
1760     * Test view_grading_table
1761     */
1762    public function test_view_grading_table_without_capability() {
1763        global $DB;
1764
1765        $this->resetAfterTest(true);
1766
1767        // Setup test data.
1768        $course = $this->getDataGenerator()->create_course();
1769        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1770        $context = context_module::instance($assign->cmid);
1771        $cm = get_coursemodule_from_instance('assign', $assign->id);
1772
1773        // Test user with no capabilities.
1774        $user = self::getDataGenerator()->create_user();
1775        $this->setUser($user);
1776        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1777        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
1778
1779        // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
1780        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1781        assign_capability('mod/assign:view', CAP_PROHIBIT, $teacherrole->id, $context->id);
1782        // Empty all the caches that may be affected by this change.
1783        accesslib_clear_all_caches_for_unit_testing();
1784        course_modinfo::clear_instance_cache();
1785
1786        $this->expectException(require_login_exception::class);
1787        $this->expectExceptionMessage('Course or activity not accessible. (Activity is hidden)');
1788        mod_assign_external::view_grading_table($assign->id);
1789    }
1790
1791    /**
1792     * Test subplugins availability
1793     */
1794    public function test_subplugins_availability() {
1795        global $CFG;
1796
1797        require_once($CFG->dirroot . '/mod/assign/adminlib.php');
1798        $this->resetAfterTest(true);
1799
1800        // Hide assignment file submissiong plugin.
1801        $pluginmanager = new assign_plugin_manager('assignsubmission');
1802        $pluginmanager->hide_plugin('file');
1803        $parameters = mod_assign_external::save_submission_parameters();
1804
1805        $this->assertTrue(!isset($parameters->keys['plugindata']->keys['files_filemanager']));
1806
1807        // Show it again and check that the value is returned as optional.
1808        $pluginmanager->show_plugin('file');
1809        $parameters = mod_assign_external::save_submission_parameters();
1810        $this->assertTrue(isset($parameters->keys['plugindata']->keys['files_filemanager']));
1811        $this->assertEquals(VALUE_OPTIONAL, $parameters->keys['plugindata']->keys['files_filemanager']->required);
1812
1813        // Hide feedback file submissiong plugin.
1814        $pluginmanager = new assign_plugin_manager('assignfeedback');
1815        $pluginmanager->hide_plugin('file');
1816
1817        $parameters = mod_assign_external::save_grade_parameters();
1818
1819        $this->assertTrue(!isset($parameters->keys['plugindata']->keys['files_filemanager']));
1820
1821        // Show it again and check that the value is returned as optional.
1822        $pluginmanager->show_plugin('file');
1823        $parameters = mod_assign_external::save_grade_parameters();
1824
1825        $this->assertTrue(isset($parameters->keys['plugindata']->keys['files_filemanager']));
1826        $this->assertEquals(VALUE_OPTIONAL, $parameters->keys['plugindata']->keys['files_filemanager']->required);
1827
1828        // Check a different one.
1829        $pluginmanager->show_plugin('comments');
1830        $this->assertTrue(isset($parameters->keys['plugindata']->keys['assignfeedbackcomments_editor']));
1831        $this->assertEquals(VALUE_OPTIONAL, $parameters->keys['plugindata']->keys['assignfeedbackcomments_editor']->required);
1832    }
1833
1834    /**
1835     * Test test_view_submission_status
1836     */
1837    public function test_view_submission_status() {
1838        global $DB;
1839
1840        $this->resetAfterTest(true);
1841
1842        $this->setAdminUser();
1843        // Setup test data.
1844        $course = $this->getDataGenerator()->create_course();
1845        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1846        $context = context_module::instance($assign->cmid);
1847        $cm = get_coursemodule_from_instance('assign', $assign->id);
1848
1849        // Test invalid instance id.
1850        try {
1851            mod_assign_external::view_submission_status(0);
1852            $this->fail('Exception expected due to invalid mod_assign instance id.');
1853        } catch (moodle_exception $e) {
1854            $this->assertEquals('invalidrecord', $e->errorcode);
1855        }
1856
1857        // Test not-enrolled user.
1858        $user = self::getDataGenerator()->create_user();
1859        $this->setUser($user);
1860        try {
1861            mod_assign_external::view_submission_status($assign->id);
1862            $this->fail('Exception expected due to not enrolled user.');
1863        } catch (moodle_exception $e) {
1864            $this->assertEquals('requireloginerror', $e->errorcode);
1865        }
1866
1867        // Test user with full capabilities.
1868        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1869        $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
1870
1871        // Trigger and capture the event.
1872        $sink = $this->redirectEvents();
1873
1874        $result = mod_assign_external::view_submission_status($assign->id);
1875        $result = external_api::clean_returnvalue(mod_assign_external::view_submission_status_returns(), $result);
1876
1877        $events = $sink->get_events();
1878        $this->assertCount(1, $events);
1879        $event = array_shift($events);
1880
1881        // Checking that the event contains the expected values.
1882        $this->assertInstanceOf('\mod_assign\event\submission_status_viewed', $event);
1883        $this->assertEquals($context, $event->get_context());
1884        $moodleurl = new \moodle_url('/mod/assign/view.php', array('id' => $cm->id));
1885        $this->assertEquals($moodleurl, $event->get_url());
1886        $this->assertEventContextNotUsed($event);
1887        $this->assertNotEmpty($event->get_name());
1888
1889        // Test user with no capabilities.
1890        // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
1891        assign_capability('mod/assign:view', CAP_PROHIBIT, $studentrole->id, $context->id);
1892        accesslib_clear_all_caches_for_unit_testing();
1893        course_modinfo::clear_instance_cache();
1894
1895        try {
1896            mod_assign_external::view_submission_status($assign->id);
1897            $this->fail('Exception expected due to missing capability.');
1898        } catch (moodle_exception $e) {
1899            $this->assertEquals('requireloginerror', $e->errorcode);
1900        }
1901    }
1902
1903    /**
1904     * Create a submission for testing the get_submission_status function.
1905     * @param  boolean $submitforgrading whether to submit for grading the submission
1906     * @return array an array containing all the required data for testing
1907     */
1908    private function create_submission_for_testing_status($submitforgrading = false) {
1909        global $DB;
1910
1911        // Create a course and assignment and users.
1912        $course = self::getDataGenerator()->create_course(array('groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1));
1913
1914        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1915        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1916
1917        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1918        $params = array(
1919            'course' => $course->id,
1920            'assignsubmission_file_maxfiles' => 1,
1921            'assignsubmission_file_maxsizebytes' => 1024 * 1024,
1922            'assignsubmission_onlinetext_enabled' => 1,
1923            'assignsubmission_file_enabled' => 1,
1924            'submissiondrafts' => 1,
1925            'assignfeedback_file_enabled' => 1,
1926            'assignfeedback_comments_enabled' => 1,
1927            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
1928            'sendnotifications' => 0
1929        );
1930
1931        set_config('submissionreceipts', 0, 'assign');
1932
1933        $instance = $generator->create_instance($params);
1934        $cm = get_coursemodule_from_instance('assign', $instance->id);
1935        $context = context_module::instance($cm->id);
1936
1937        $assign = new mod_assign_testable_assign($context, $cm, $course);
1938
1939        $student1 = self::getDataGenerator()->create_user();
1940        $student2 = self::getDataGenerator()->create_user();
1941        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1942        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1943        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
1944        $teacher = self::getDataGenerator()->create_user();
1945        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1946        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1947
1948        $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $student1->id));
1949        $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $teacher->id));
1950        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $student2->id));
1951        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $teacher->id));
1952
1953        $this->setUser($student1);
1954
1955        // Create a student1 with an online text submission.
1956        // Simulate a submission.
1957        $submission = $assign->get_user_submission($student1->id, true);
1958
1959        $data = new stdClass();
1960        $data->onlinetext_editor = array(
1961            'itemid' => file_get_unused_draft_itemid(),
1962            'text' => 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>',
1963            'format' => FORMAT_MOODLE);
1964
1965        $draftidfile = file_get_unused_draft_itemid();
1966        $usercontext = context_user::instance($student1->id);
1967        $filerecord = array(
1968            'contextid' => $usercontext->id,
1969            'component' => 'user',
1970            'filearea'  => 'draft',
1971            'itemid'    => $draftidfile,
1972            'filepath'  => '/',
1973            'filename'  => 't.txt',
1974        );
1975        $fs = get_file_storage();
1976        $fs->create_file_from_string($filerecord, 'text contents');
1977
1978        $data->files_filemanager = $draftidfile;
1979
1980        $notices = array();
1981        $assign->save_submission($data, $notices);
1982
1983        if ($submitforgrading) {
1984            // Now, submit the draft for grading.
1985            $notices = array();
1986
1987            $data = new stdClass;
1988            $data->userid = $student1->id;
1989            $assign->submit_for_grading($data, $notices);
1990        }
1991
1992        return array($assign, $instance, $student1, $student2, $teacher, $group1, $group2);
1993    }
1994
1995    /**
1996     * Test get_submission_status for a draft submission.
1997     */
1998    public function test_get_submission_status_in_draft_status() {
1999        $this->resetAfterTest(true);
2000
2001        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();
2002        $studentsubmission = $assign->get_user_submission($student1->id, true);
2003
2004        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2005        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2006        $this->assertDebuggingCalled();
2007
2008        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2009
2010        // The submission is now in draft mode.
2011        $this->assertCount(0, $result['warnings']);
2012        $this->assertFalse(isset($result['gradingsummary']));
2013        $this->assertFalse(isset($result['feedback']));
2014        $this->assertFalse(isset($result['previousattempts']));
2015
2016        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2017        $this->assertTrue($result['lastattempt']['canedit']);
2018        $this->assertTrue($result['lastattempt']['cansubmit']);
2019        $this->assertFalse($result['lastattempt']['locked']);
2020        $this->assertFalse($result['lastattempt']['graded']);
2021        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2022        $this->assertFalse($result['lastattempt']['blindmarking']);
2023        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2024        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2025
2026        $this->assertEquals($student1->id, $result['lastattempt']['submission']['userid']);
2027        $this->assertEquals(0, $result['lastattempt']['submission']['attemptnumber']);
2028        $this->assertEquals('draft', $result['lastattempt']['submission']['status']);
2029        $this->assertEquals(0, $result['lastattempt']['submission']['groupid']);
2030        $this->assertEquals($assign->get_instance()->id, $result['lastattempt']['submission']['assignment']);
2031        $this->assertEquals(1, $result['lastattempt']['submission']['latest']);
2032
2033        // Map plugins based on their type - we can't rely on them being in a
2034        // particular order, especially if 3rd party plugins are installed.
2035        $submissionplugins = array();
2036        foreach ($result['lastattempt']['submission']['plugins'] as $plugin) {
2037            $submissionplugins[$plugin['type']] = $plugin;
2038        }
2039
2040        // Format expected online text.
2041        $onlinetext = 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>';
2042        list($expectedtext, $expectedformat) = external_format_text($onlinetext, FORMAT_HTML, $assign->get_context()->id,
2043                'assignsubmission_onlinetext', ASSIGNSUBMISSION_ONLINETEXT_FILEAREA, $studentsubmission->id);
2044
2045        $this->assertEquals($expectedtext, $submissionplugins['onlinetext']['editorfields'][0]['text']);
2046        $this->assertEquals($expectedformat, $submissionplugins['onlinetext']['editorfields'][0]['format']);
2047        $this->assertEquals('/', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);
2048        $this->assertEquals('t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filename']);
2049    }
2050
2051    /**
2052     * Test get_submission_status for a submitted submission.
2053     */
2054    public function test_get_submission_status_in_submission_status() {
2055        $this->resetAfterTest(true);
2056
2057        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);
2058
2059        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2060        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2061        $this->assertDebuggingCalled();
2062        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2063
2064        $this->assertCount(0, $result['warnings']);
2065        $this->assertFalse(isset($result['gradingsummary']));
2066        $this->assertFalse(isset($result['feedback']));
2067        $this->assertFalse(isset($result['previousattempts']));
2068
2069        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2070        $this->assertFalse($result['lastattempt']['canedit']);
2071        $this->assertFalse($result['lastattempt']['cansubmit']);
2072        $this->assertFalse($result['lastattempt']['locked']);
2073        $this->assertFalse($result['lastattempt']['graded']);
2074        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2075        $this->assertFalse($result['lastattempt']['blindmarking']);
2076        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2077        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2078
2079    }
2080
2081    /**
2082     * Test get_submission_status using the teacher role.
2083     */
2084    public function test_get_submission_status_in_submission_status_for_teacher() {
2085        global $DB;
2086        $this->resetAfterTest(true);
2087
2088        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);
2089
2090        // Now, as teacher, see the grading summary.
2091        $this->setUser($teacher);
2092        // First one group.
2093        $result = mod_assign_external::get_submission_status($assign->get_instance()->id, 0, $g1->id);
2094        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2095        $this->assertDebuggingCalled();
2096        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2097
2098        $this->assertCount(0, $result['warnings']);
2099        $this->assertFalse(isset($result['lastattempt']));
2100        $this->assertFalse(isset($result['feedback']));
2101        $this->assertFalse(isset($result['previousattempts']));
2102
2103        $this->assertEquals(1, $result['gradingsummary']['participantcount']);
2104        $this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);
2105        $this->assertEquals(1, $result['gradingsummary']['submissionsenabled']);
2106        $this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);
2107        $this->assertEquals(1, $result['gradingsummary']['submissionssubmittedcount']);  // One student from G1 submitted.
2108        $this->assertEquals(1, $result['gradingsummary']['submissionsneedgradingcount']);    // One student from G1 submitted.
2109        $this->assertEmpty($result['gradingsummary']['warnofungroupedusers']);
2110
2111        // Second group.
2112        $result = mod_assign_external::get_submission_status($assign->get_instance()->id, 0, $g2->id);
2113        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2114        $this->assertCount(0, $result['warnings']);
2115        $this->assertEquals(1, $result['gradingsummary']['participantcount']);
2116        $this->assertEquals(0, $result['gradingsummary']['submissionssubmittedcount']); // G2 students didn't submit yet.
2117        $this->assertEquals(0, $result['gradingsummary']['submissionsneedgradingcount']);   // G2 students didn't submit yet.
2118
2119        // Should not return information for all users (missing access to all groups capability for non-editing teacher).
2120        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2121        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2122        $this->assertCount(0, $result['warnings']);
2123        $this->assertFalse(isset($result['gradingsummary']));
2124
2125        // Should return all participants if we grant accessallgroups capability to the normal teacher role.
2126        $context = context_course::instance($assign->get_instance()->course);
2127        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2128        assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id, true);
2129        accesslib_clear_all_caches_for_unit_testing();
2130
2131        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2132        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2133        $this->assertCount(0, $result['warnings']);
2134        $this->assertEquals(2, $result['gradingsummary']['participantcount']);
2135        $this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);
2136        $this->assertEquals(1, $result['gradingsummary']['submissionssubmittedcount']); // One student from G1 submitted.
2137        $this->assertEquals(1, $result['gradingsummary']['submissionsneedgradingcount']); // One student from G1 submitted.
2138
2139        // Now check draft submissions.
2140        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(false);
2141        $this->setUser($teacher);
2142        $result = mod_assign_external::get_submission_status($assign->get_instance()->id, 0, $g1->id);
2143        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2144        $this->assertCount(0, $result['warnings']);
2145        $this->assertEquals(1, $result['gradingsummary']['participantcount']);
2146        $this->assertEquals(1, $result['gradingsummary']['submissiondraftscount']); // We have a draft submission.
2147        $this->assertEquals(0, $result['gradingsummary']['submissionssubmittedcount']); // We have only draft submissions.
2148        $this->assertEquals(0, $result['gradingsummary']['submissionsneedgradingcount']); // We have only draft submissions.
2149    }
2150
2151    /**
2152     * Test get_submission_status for a reopened submission.
2153     */
2154    public function test_get_submission_status_in_reopened_status() {
2155        global $USER;
2156
2157        $this->resetAfterTest(true);
2158
2159        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);
2160        $studentsubmission = $assign->get_user_submission($student1->id, true);
2161
2162        $this->setUser($teacher);
2163        // Grade and reopen.
2164        $feedbackpluginparams = array();
2165        $feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
2166        $feedbackeditorparams = array('text' => 'Yeeha!',
2167                                        'format' => 1);
2168        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
2169        $result = mod_assign_external::save_grade(
2170            $instance->id,
2171            $student1->id,
2172            50.0,
2173            -1,
2174            false,
2175            'released',
2176            false,
2177            $feedbackpluginparams);
2178        $USER->ignoresesskey = true;
2179        $assign->testable_process_add_attempt($student1->id);
2180
2181        $this->setUser($student1);
2182
2183        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2184        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2185        $this->assertDebuggingCalled();
2186        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2187
2188        $this->assertCount(0, $result['warnings']);
2189        $this->assertFalse(isset($result['gradingsummary']));
2190
2191        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2192        $this->assertTrue($result['lastattempt']['canedit']);
2193        $this->assertFalse($result['lastattempt']['cansubmit']);
2194        $this->assertFalse($result['lastattempt']['locked']);
2195        $this->assertFalse($result['lastattempt']['graded']);
2196        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2197        $this->assertFalse($result['lastattempt']['blindmarking']);
2198        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2199        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2200
2201        // Check new attempt reopened.
2202        $this->assertEquals($student1->id, $result['lastattempt']['submission']['userid']);
2203        $this->assertEquals(1, $result['lastattempt']['submission']['attemptnumber']);
2204        $this->assertEquals('reopened', $result['lastattempt']['submission']['status']);
2205        $this->assertEquals(0, $result['lastattempt']['submission']['groupid']);
2206        $this->assertEquals($assign->get_instance()->id, $result['lastattempt']['submission']['assignment']);
2207        $this->assertEquals(1, $result['lastattempt']['submission']['latest']);
2208        $this->assertCount(3, $result['lastattempt']['submission']['plugins']);
2209
2210        // Now see feedback and the attempts history (remember, is a submission reopened).
2211        // Only 2 fields (no grade, no plugins data).
2212        $this->assertCount(2, $result['feedback']);
2213
2214        // One previous attempt.
2215        $this->assertCount(1, $result['previousattempts']);
2216        $this->assertEquals(0, $result['previousattempts'][0]['attemptnumber']);
2217        $this->assertEquals(50, $result['previousattempts'][0]['grade']['grade']);
2218        $this->assertEquals($teacher->id, $result['previousattempts'][0]['grade']['grader']);
2219        $this->assertEquals($student1->id, $result['previousattempts'][0]['grade']['userid']);
2220
2221        // Map plugins based on their type - we can't rely on them being in a
2222        // particular order, especially if 3rd party plugins are installed.
2223        $feedbackplugins = array();
2224        foreach ($result['previousattempts'][0]['feedbackplugins'] as $plugin) {
2225            $feedbackplugins[$plugin['type']] = $plugin;
2226        }
2227        $this->assertEquals('Yeeha!', $feedbackplugins['comments']['editorfields'][0]['text']);
2228
2229        $submissionplugins = array();
2230        foreach ($result['previousattempts'][0]['submission']['plugins'] as $plugin) {
2231            $submissionplugins[$plugin['type']] = $plugin;
2232        }
2233        // Format expected online text.
2234        $onlinetext = 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>';
2235        list($expectedtext, $expectedformat) = external_format_text($onlinetext, FORMAT_HTML, $assign->get_context()->id,
2236                'assignsubmission_onlinetext', ASSIGNSUBMISSION_ONLINETEXT_FILEAREA, $studentsubmission->id);
2237
2238        $this->assertEquals($expectedtext, $submissionplugins['onlinetext']['editorfields'][0]['text']);
2239        $this->assertEquals($expectedformat, $submissionplugins['onlinetext']['editorfields'][0]['format']);
2240        $this->assertEquals('/', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);
2241        $this->assertEquals('t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filename']);
2242
2243    }
2244
2245    /**
2246     * Test access control for get_submission_status.
2247     */
2248    public function test_get_submission_status_access_control() {
2249        $this->resetAfterTest(true);
2250
2251        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();
2252
2253        $this->setUser($student2);
2254
2255        // Access control test.
2256        $this->expectException(required_capability_exception::class);
2257        mod_assign_external::get_submission_status($assign->get_instance()->id, $student1->id);
2258
2259    }
2260
2261    /**
2262     * Test hidden grader for get_submission_status.
2263     */
2264    public function test_get_submission_status_hidden_grader() {
2265        $this->resetAfterTest(true);
2266
2267        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);
2268
2269        // Grade the assign for the student1.
2270        $this->setUser($teacher);
2271
2272        $data = new stdClass();
2273        $data->grade = '50.0';
2274        $data->assignfeedbackcomments_editor = ['text' => ''];
2275        $assign->testable_apply_grade_to_user($data, $student1->id, 0);
2276
2277        $this->setUser($student1);
2278
2279        // Check that the student can see the grader by default.
2280        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2281        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2282        $this->assertDebuggingCalled();
2283
2284        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2285
2286        $this->assertTrue(isset($result['feedback']));
2287        $this->assertTrue(isset($result['feedback']['grade']));
2288        $this->assertEquals($teacher->id, $result['feedback']['grade']['grader']);
2289
2290        // Now change the setting so the grader is hidden.
2291        $this->setAdminUser();
2292
2293        $instance = $assign->get_instance();
2294        $instance->instance = $instance->id;
2295        $instance->hidegrader = true;
2296        $assign->update_instance($instance);
2297
2298        $this->setUser($student1);
2299
2300        // Check that the student cannot see the grader anymore.
2301        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2302        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2303
2304        $this->assertTrue(isset($result['feedback']));
2305        $this->assertTrue(isset($result['feedback']['grade']));
2306        $this->assertEquals(-1, $result['feedback']['grade']['grader']);
2307
2308        // Check that the teacher can see the grader.
2309        $this->setUser($teacher);
2310
2311        $result = mod_assign_external::get_submission_status($assign->get_instance()->id, $student1->id);
2312        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2313
2314        $this->assertTrue(isset($result['feedback']));
2315        $this->assertTrue(isset($result['feedback']['grade']));
2316        $this->assertEquals($teacher->id, $result['feedback']['grade']['grader']);
2317    }
2318
2319    /**
2320     * Test get_submission_status with override for student.
2321     */
2322    public function test_get_submission_status_with_override() {
2323        global $DB;
2324
2325        $this->resetAfterTest(true);
2326
2327        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();
2328
2329        $overridedata = new \stdClass();
2330        $overridedata->assignid = $assign->get_instance()->id;
2331        $overridedata->userid = $student1->id;
2332        $overridedata->allowsubmissionsfromdate = time() + YEARSECS;
2333        $DB->insert_record('assign_overrides', $overridedata);
2334
2335        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2336        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2337        $this->assertDebuggingCalled();
2338        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2339
2340        $this->assertCount(0, $result['warnings']);
2341        $this->assertFalse(isset($result['gradingsummary']));
2342        $this->assertFalse(isset($result['feedback']));
2343        $this->assertFalse(isset($result['previousattempts']));
2344
2345        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2346        $this->assertFalse($result['lastattempt']['canedit']);  // False because of override.
2347        $this->assertFalse($result['lastattempt']['cansubmit']);
2348        $this->assertFalse($result['lastattempt']['locked']);
2349        $this->assertFalse($result['lastattempt']['graded']);
2350        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2351        $this->assertFalse($result['lastattempt']['blindmarking']);
2352        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2353        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2354
2355        // Same assignment but user without override.
2356        $this->setUser($student2);
2357
2358        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2359        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2360
2361        // The submission is now in draft mode.
2362        $this->assertCount(0, $result['warnings']);
2363        $this->assertFalse(isset($result['gradingsummary']));
2364        $this->assertFalse(isset($result['feedback']));
2365        $this->assertFalse(isset($result['previousattempts']));
2366
2367        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2368        $this->assertTrue($result['lastattempt']['canedit']);  // True because there is not override for this user.
2369        $this->assertFalse($result['lastattempt']['cansubmit']);
2370        $this->assertFalse($result['lastattempt']['locked']);
2371        $this->assertFalse($result['lastattempt']['graded']);
2372        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2373        $this->assertFalse($result['lastattempt']['blindmarking']);
2374        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2375        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2376    }
2377
2378    /**
2379     * get_participant should throw an excaption if the requested assignment doesn't exist.
2380     */
2381    public function test_get_participant_no_assignment() {
2382        $this->resetAfterTest(true);
2383        $this->expectException(moodle_exception::class);
2384        mod_assign_external::get_participant('-1', '-1', false);
2385    }
2386
2387    /**
2388     * get_participant should throw a require_login_exception if the user doesn't have access
2389     * to view assignments.
2390     */
2391    public function test_get_participant_no_view_capability() {
2392        global $DB;
2393        $this->resetAfterTest(true);
2394
2395        $result = $this->create_assign_with_student_and_teacher();
2396        $assign = $result['assign'];
2397        $student = $result['student'];
2398        $course = $result['course'];
2399        $context = context_course::instance($course->id);
2400        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2401
2402        $this->setUser($student);
2403        assign_capability('mod/assign:view', CAP_PROHIBIT, $studentrole->id, $context->id, true);
2404
2405        $this->expectException(require_login_exception::class);
2406        mod_assign_external::get_participant($assign->id, $student->id, false);
2407    }
2408
2409    /**
2410     * get_participant should throw a required_capability_exception if the user doesn't have access
2411     * to view assignment grades.
2412     */
2413    public function test_get_participant_no_grade_capability() {
2414        global $DB;
2415        $this->resetAfterTest(true);
2416
2417        $result = $this->create_assign_with_student_and_teacher();
2418        $assign = $result['assign'];
2419        $student = $result['student'];
2420        $teacher = $result['teacher'];
2421        $course = $result['course'];
2422        $context = context_course::instance($course->id);
2423        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2424
2425        $this->setUser($teacher);
2426        assign_capability('mod/assign:viewgrades', CAP_PROHIBIT, $teacherrole->id, $context->id, true);
2427        assign_capability('mod/assign:grade', CAP_PROHIBIT, $teacherrole->id, $context->id, true);
2428        accesslib_clear_all_caches_for_unit_testing();
2429
2430        $this->expectException(required_capability_exception::class);
2431        mod_assign_external::get_participant($assign->id, $student->id, false);
2432    }
2433
2434    /**
2435     * get_participant should throw an exception if the user isn't enrolled in the course.
2436     */
2437    public function test_get_participant_no_participant() {
2438        global $DB;
2439        $this->resetAfterTest(true);
2440
2441        $result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true));
2442        $student = $this->getDataGenerator()->create_user();
2443        $assign = $result['assign'];
2444        $teacher = $result['teacher'];
2445
2446        $this->setUser($teacher);
2447
2448        $this->expectException(moodle_exception::class);
2449        $result = mod_assign_external::get_participant($assign->id, $student->id, false);
2450        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2451    }
2452
2453    /**
2454     * get_participant should return a summarised list of details with a different fullname if blind
2455     * marking is on for the requested assignment.
2456     */
2457    public function test_get_participant_blind_marking() {
2458        global $DB;
2459        $this->resetAfterTest(true);
2460
2461        $result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true));
2462        $assign = $result['assign'];
2463        $student = $result['student'];
2464        $teacher = $result['teacher'];
2465        $course = $result['course'];
2466        $context = context_course::instance($course->id);
2467        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2468
2469        $this->setUser($teacher);
2470
2471        $result = mod_assign_external::get_participant($assign->id, $student->id, true);
2472        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2473        $this->assertEquals($student->id, $result['id']);
2474        $this->assertFalse(fullname($student) == $result['fullname']);
2475        $this->assertFalse($result['submitted']);
2476        $this->assertFalse($result['requiregrading']);
2477        $this->assertFalse($result['grantedextension']);
2478        $this->assertTrue($result['blindmarking']);
2479        // Make sure we don't get any additional info.
2480        $this->assertArrayNotHasKey('user', $result);
2481    }
2482
2483    /**
2484     * get_participant should return a summarised list of details if requested.
2485     */
2486    public function test_get_participant_no_user() {
2487        global $DB;
2488        $this->resetAfterTest(true);
2489
2490        $result = $this->create_assign_with_student_and_teacher();
2491        $assignmodule = $result['assign'];
2492        $student = $result['student'];
2493        $teacher = $result['teacher'];
2494        $course = $result['course'];
2495        $context = context_course::instance($course->id);
2496        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2497
2498        // Create an assign instance to save a submission.
2499        set_config('submissionreceipts', 0, 'assign');
2500
2501        $cm = get_coursemodule_from_instance('assign', $assignmodule->id);
2502        $context = context_module::instance($cm->id);
2503
2504        $assign = new assign($context, $cm, $course);
2505
2506        $this->setUser($student);
2507
2508        // Simulate a submission.
2509        $data = new stdClass();
2510        $data->onlinetext_editor = array(
2511            'itemid' => file_get_unused_draft_itemid(),
2512            'text' => 'Student submission text',
2513            'format' => FORMAT_MOODLE
2514        );
2515
2516        $notices = array();
2517        $assign->save_submission($data, $notices);
2518
2519        $data = new stdClass;
2520        $data->userid = $student->id;
2521        $assign->submit_for_grading($data, array());
2522
2523        $this->setUser($teacher);
2524
2525        $result = mod_assign_external::get_participant($assignmodule->id, $student->id, false);
2526        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2527        $this->assertEquals($student->id, $result['id']);
2528        $this->assertEquals(fullname($student), $result['fullname']);
2529        $this->assertTrue($result['submitted']);
2530        $this->assertTrue($result['requiregrading']);
2531        $this->assertFalse($result['grantedextension']);
2532        $this->assertFalse($result['blindmarking']);
2533        // Make sure we don't get any additional info.
2534        $this->assertArrayNotHasKey('user', $result);
2535    }
2536
2537    /**
2538     * get_participant should return user details if requested.
2539     */
2540    public function test_get_participant_full_details() {
2541        global $DB;
2542        $this->resetAfterTest(true);
2543
2544        $result = $this->create_assign_with_student_and_teacher();
2545        $assign = $result['assign'];
2546        $student = $result['student'];
2547        $teacher = $result['teacher'];
2548        $course = $result['course'];
2549        $context = context_course::instance($course->id);
2550        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2551
2552        $this->setUser($teacher);
2553
2554        $result = mod_assign_external::get_participant($assign->id, $student->id, true);
2555        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2556        // Check some of the extended properties we get when requesting the user.
2557        $this->assertEquals($student->id, $result['id']);
2558        // We should get user infomation back.
2559        $user = $result['user'];
2560        $this->assertFalse(empty($user));
2561        $this->assertEquals($student->firstname, $user['firstname']);
2562        $this->assertEquals($student->lastname, $user['lastname']);
2563        $this->assertEquals($student->email, $user['email']);
2564    }
2565
2566    /**
2567     * get_participant should return group details if a group submission was
2568     * submitted.
2569     */
2570    public function test_get_participant_group_submission() {
2571        global $DB;
2572
2573        $this->resetAfterTest(true);
2574
2575        $result = $this->create_assign_with_student_and_teacher(array(
2576            'assignsubmission_onlinetext_enabled' => 1,
2577            'teamsubmission' => 1
2578        ));
2579        $assignmodule = $result['assign'];
2580        $student = $result['student'];
2581        $teacher = $result['teacher'];
2582        $course = $result['course'];
2583        $context = context_course::instance($course->id);
2584        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2585        $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
2586        $cm = get_coursemodule_from_instance('assign', $assignmodule->id);
2587        $context = context_module::instance($cm->id);
2588        $assign = new mod_assign_testable_assign($context, $cm, $course);
2589
2590        groups_add_member($group, $student);
2591
2592        $this->setUser($student);
2593        $submission = $assign->get_group_submission($student->id, $group->id, true);
2594        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2595        $assign->testable_update_submission($submission, $student->id, true, false);
2596        $data = new stdClass();
2597        $data->onlinetext_editor = array(
2598            'itemid' => file_get_unused_draft_itemid(),
2599            'text' => 'Submission text',
2600            'format' => FORMAT_MOODLE);
2601        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
2602        $plugin->save($submission, $data);
2603
2604        $this->setUser($teacher);
2605
2606        $result = mod_assign_external::get_participant($assignmodule->id, $student->id, false);
2607        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2608        // Check some of the extended properties we get when not requesting a summary.
2609        $this->assertEquals($student->id, $result['id']);
2610        $this->assertEquals($group->id, $result['groupid']);
2611        $this->assertEquals($group->name, $result['groupname']);
2612    }
2613
2614    /**
2615     * Test get_participant() when relative dates mode is enabled on the course.
2616     *
2617     * @dataProvider get_participant_relative_dates_provider
2618     * @param array $courseconfig the config to use when creating the course.
2619     * @param array $assignconfig the config to use when creating the assignment.
2620     * @param array $enrolconfig the enrolement to create.
2621     * @param array $expectedproperties array of expected assign properties.
2622     */
2623    public function test_get_participant_relative_dates(array $courseconfig, array $assignconfig, array $enrolconfig,
2624            array $expectedproperties) {
2625        $this->resetAfterTest();
2626
2627        set_config('enablecourserelativedates', true); // Enable relative dates at site level.
2628
2629        $course = $this->getDataGenerator()->create_course($courseconfig);
2630        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
2631        $assignconfig['course'] = $course->id;
2632        $instance = $generator->create_instance($assignconfig);
2633        $cm = get_coursemodule_from_instance('assign', $instance->id);
2634        $context = context_module::instance($cm->id);
2635        $assign = new assign($context, $cm, $course);
2636
2637        $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig));
2638
2639        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher', null, 'manual', time() - 50 * DAYSECS);
2640
2641        $this->setUser($teacher);
2642        $result = mod_assign_external::get_participant($assign->get_instance()->id, $user->id, false);
2643        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2644
2645        foreach ($expectedproperties as $propertyname => $propertyval) {
2646            $this->assertEquals($propertyval, $result[$propertyname]);
2647        }
2648    }
2649
2650    /**
2651     * The test_get_participant_relative_dates data provider.
2652     */
2653    public function get_participant_relative_dates_provider() {
2654        $timenow = time();
2655
2656        return [
2657            'Student whose enrolment starts after the course start date, relative dates mode enabled' => [
2658                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
2659                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
2660                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
2661                    'startdate' => $timenow - 8 * DAYSECS],
2662                'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS]
2663            ],
2664            'Student whose enrolment starts before the course start date, relative dates mode enabled' => [
2665                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
2666                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
2667                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
2668                    'startdate' => $timenow - 12 * DAYSECS],
2669                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
2670            ],
2671        ];
2672    }
2673
2674    /**
2675     * Test for mod_assign_external::list_participants().
2676     *
2677     * @throws coding_exception
2678     */
2679    public function test_list_participants_user_info_with_special_characters() {
2680        global $CFG, $DB;
2681        $this->resetAfterTest(true);
2682        $CFG->showuseridentity = 'idnumber,email,phone1,phone2,department,institution';
2683
2684        $data = $this->create_assign_with_student_and_teacher();
2685        $assignment = $data['assign'];
2686        $teacher = $data['teacher'];
2687
2688        // Set data for student info that contain special characters.
2689        $student = $data['student'];
2690        $student->idnumber = '<\'"1am@wesome&c00l"\'>';
2691        $student->phone1 = '+63 (999) 888-7777';
2692        $student->phone2 = '(011) [15]4-123-4567';
2693        $student->department = 'Arts & Sciences & \' " ¢ £ © € ¥ ® < >';
2694        $student->institution = 'University of Awesome People & \' " ¢ £ © € ¥ ® < >';
2695        // Assert that we have valid user data.
2696        $this->assertTrue(core_user::validate($student));
2697        // Update the user record.
2698        $DB->update_record('user', $student);
2699
2700        $this->setUser($teacher);
2701        $participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, true, true);
2702        $participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
2703        $this->assertCount(1, $participants);
2704
2705        // Asser that we have a valid response data.
2706        $response = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
2707        $this->assertEquals($response, $participants);
2708
2709        // Check participant data.
2710        $participant = $participants[0];
2711        $this->assertEquals($student->idnumber, $participant['idnumber']);
2712        $this->assertEquals($student->email, $participant['email']);
2713        $this->assertEquals($student->phone1, $participant['phone1']);
2714        $this->assertEquals($student->phone2, $participant['phone2']);
2715        $this->assertEquals($student->department, $participant['department']);
2716        $this->assertEquals($student->institution, $participant['institution']);
2717        $this->assertArrayHasKey('enrolledcourses', $participant);
2718
2719        $participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, false, true);
2720        $participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
2721        // Check that the list of courses the participant is enrolled is not returned.
2722        $participant = $participants[0];
2723        $this->assertArrayNotHasKey('enrolledcourses', $participant);
2724    }
2725
2726    /**
2727     * Test for the type of the user-related properties in mod_assign_external::list_participants_returns().
2728     */
2729    public function test_list_participants_returns_user_property_types() {
2730        // Get user properties.
2731        $userdesc = core_user_external::user_description();
2732        $this->assertTrue(isset($userdesc->keys));
2733        $userproperties = array_keys($userdesc->keys);
2734
2735        // Get returns description for mod_assign_external::list_participants_returns().
2736        $listreturns = mod_assign_external::list_participants_returns();
2737        $this->assertTrue(isset($listreturns->content));
2738        $listreturnsdesc = $listreturns->content->keys;
2739
2740        // Iterate over list returns description's keys.
2741        foreach ($listreturnsdesc as $key => $desc) {
2742            // Check if key exists in user properties and the description has a type attribute.
2743            if (in_array($key, $userproperties) && isset($desc->type)) {
2744                try {
2745                    // The core_user::get_property_type() method might throw a coding_exception since
2746                    // core_user_external::user_description() might contain properties that are not yet included in
2747                    // core_user's $propertiescache.
2748                    $propertytype = core_user::get_property_type($key);
2749
2750                    // Assert that user-related property types match those of the defined in core_user.
2751                    $this->assertEquals($propertytype, $desc->type);
2752                } catch (coding_exception $e) {
2753                    // All good.
2754                }
2755            }
2756        }
2757    }
2758
2759    /**
2760     * Create a a course, assignment module instance, student and teacher and enrol them in
2761     * the course.
2762     *
2763     * @param array $params parameters to be provided to the assignment module creation
2764     * @return array containing the course, assignment module, student and teacher
2765     */
2766    private function create_assign_with_student_and_teacher($params = array()) {
2767        global $DB;
2768
2769        $course = $this->getDataGenerator()->create_course();
2770        $params = array_merge(array(
2771            'course' => $course->id,
2772            'name' => 'assignment',
2773            'intro' => 'assignment intro text',
2774        ), $params);
2775
2776        // Create a course and assignment and users.
2777        $assign = $this->getDataGenerator()->create_module('assign', $params);
2778
2779        $cm = get_coursemodule_from_instance('assign', $assign->id);
2780        $context = context_module::instance($cm->id);
2781
2782        $student = $this->getDataGenerator()->create_user();
2783        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2784        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
2785        $teacher = $this->getDataGenerator()->create_user();
2786        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2787        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
2788
2789        assign_capability('mod/assign:view', CAP_ALLOW, $teacherrole->id, $context->id, true);
2790        assign_capability('mod/assign:viewgrades', CAP_ALLOW, $teacherrole->id, $context->id, true);
2791        assign_capability('mod/assign:grade', CAP_ALLOW, $teacherrole->id, $context->id, true);
2792        accesslib_clear_all_caches_for_unit_testing();
2793
2794        return array(
2795            'course' => $course,
2796            'assign' => $assign,
2797            'student' => $student,
2798            'teacher' => $teacher
2799        );
2800    }
2801
2802    /**
2803     * Test test_view_assign
2804     */
2805    public function test_view_assign() {
2806        global $CFG;
2807
2808        $CFG->enablecompletion = 1;
2809        $this->resetAfterTest();
2810
2811        $this->setAdminUser();
2812        // Setup test data.
2813        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
2814        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id),
2815                                                            array('completion' => 2, 'completionview' => 1));
2816        $context = context_module::instance($assign->cmid);
2817        $cm = get_coursemodule_from_instance('assign', $assign->id);
2818
2819        $result = mod_assign_external::view_assign($assign->id);
2820        $result = external_api::clean_returnvalue(mod_assign_external::view_assign_returns(), $result);
2821        $this->assertTrue($result['status']);
2822        $this->assertEmpty($result['warnings']);
2823
2824        // Check completion status.
2825        $completion = new completion_info($course);
2826        $completiondata = $completion->get_data($cm);
2827        $this->assertEquals(1, $completiondata->completionstate);
2828    }
2829}
2830