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