Rev 1 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>.namespace mod_assign;use core_external\external_api;use core_external\external_settings;use core_user_external;use mod_assign_external;use mod_assign_testable_assign;defined('MOODLE_INTERNAL') || die();global $CFG;require_once($CFG->dirroot . '/webservice/tests/helpers.php');require_once($CFG->dirroot . '/mod/assign/externallib.php');require_once($CFG->dirroot . '/mod/assign/tests/externallib_advanced_testcase.php');require_once(__DIR__ . '/fixtures/testable_assign.php');/*** External mod assign functions unit tests** @package mod_assign* @category external* @copyright 2012 Paul Charsley* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class externallib_test extends \mod_assign\externallib_advanced_testcase {/*** Test get_grades*/public function test_get_grades(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment.$coursedata['idnumber'] = 'idnumbercourse';$coursedata['fullname'] = 'Lightwork Course';$coursedata['summary'] = 'Lightwork Course description';$coursedata['summaryformat'] = FORMAT_MOODLE;$course = self::getDataGenerator()->create_course($coursedata);$assigndata['course'] = $course->id;$assigndata['name'] = 'lightwork assignment';$assign = self::getDataGenerator()->create_module('assign', $assigndata);// Create a manual enrolment record.$manualenroldata['enrol'] = 'manual';$manualenroldata['status'] = 0;$manualenroldata['courseid'] = $course->id;$enrolid = $DB->insert_record('enrol', $manualenroldata);// Create a teacher and give them capabilities.$context = \context_course::instance($course->id);$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);$context = \context_module::instance($assign->cmid);$this->assignUserCapability('mod/assign:viewgrades', $context->id, $roleid);// Create the teacher's enrolment record.$userenrolmentdata['status'] = 0;$userenrolmentdata['enrolid'] = $enrolid;$userenrolmentdata['userid'] = $USER->id;$DB->insert_record('user_enrolments', $userenrolmentdata);// Create a student and give them 2 grades (for 2 attempts).$student = self::getDataGenerator()->create_user();$submission = new \stdClass();$submission->assignment = $assign->id;$submission->userid = $student->id;$submission->status = ASSIGN_SUBMISSION_STATUS_NEW;$submission->latest = 0;$submission->attemptnumber = 0;$submission->groupid = 0;$submission->timecreated = time();$submission->timemodified = time();$DB->insert_record('assign_submission', $submission);$grade = new \stdClass();$grade->assignment = $assign->id;$grade->userid = $student->id;$grade->timecreated = time();$grade->timemodified = $grade->timecreated;$grade->grader = $USER->id;$grade->grade = 50;$grade->attemptnumber = 0;$DB->insert_record('assign_grades', $grade);$submission = new \stdClass();$submission->assignment = $assign->id;$submission->userid = $student->id;$submission->status = ASSIGN_SUBMISSION_STATUS_NEW;$submission->latest = 1;$submission->attemptnumber = 1;$submission->groupid = 0;$submission->timecreated = time();$submission->timemodified = time();$DB->insert_record('assign_submission', $submission);$grade = new \stdClass();$grade->assignment = $assign->id;$grade->userid = $student->id;$grade->timecreated = time();$grade->timemodified = $grade->timecreated;$grade->grader = $USER->id;$grade->grade = 75;$grade->attemptnumber = 1;$DB->insert_record('assign_grades', $grade);$assignmentids[] = $assign->id;$result = mod_assign_external::get_grades($assignmentids);// We need to execute the return values cleaning process to simulate the web service server.$result = external_api::clean_returnvalue(mod_assign_external::get_grades_returns(), $result);// Check that the correct grade information for the student is returned.$this->assertEquals(1, count($result['assignments']));$assignment = $result['assignments'][0];$this->assertEquals($assign->id, $assignment['assignmentid']);// Should only get the last grade for this student.$this->assertEquals(1, count($assignment['grades']));$grade = $assignment['grades'][0];$this->assertEquals($student->id, $grade['userid']);// Should be the last grade (not the first).$this->assertEquals(75, $grade['grade']);}/*** Test get_assignments*/public function test_get_assignments(): void {global $DB, $USER, $CFG;$this->resetAfterTest(true);// Enable multilang filter to on content and heading.filter_set_global_state('multilang', TEXTFILTER_ON);filter_set_applies_to_strings('multilang', 1);// Set WS filtering.$wssettings = external_settings::get_instance();$wssettings->set_filter(true);$category = self::getDataGenerator()->create_category(array('name' => 'Test category'));// Create a course.$course1 = self::getDataGenerator()->create_course(array('idnumber' => 'idnumbercourse1','fullname' => '<b>Lightwork Course 1</b>', // Adding tags here to check that \core_external\util::format_string works.'shortname' => '<b>Lightwork Course 1</b>', // Adding tags here to check that \core_external\util::format_string works.'summary' => 'Lightwork Course 1 description','summaryformat' => FORMAT_MOODLE,'category' => $category->id));// Create a second course, just for testing.$course2 = self::getDataGenerator()->create_course(array('idnumber' => 'idnumbercourse2','fullname' => 'Lightwork Course 2','summary' => 'Lightwork Course 2 description','summaryformat' => FORMAT_MOODLE,'category' => $category->id));// Create the assignment module with links to a filerecord.$assign1 = self::getDataGenerator()->create_module('assign', array('course' => $course1->id,'name' => '<span lang="en" class="multilang">English</span><span lang="es" class="multilang">Español</span>','intro' => 'the assignment intro text here <a href="@@PLUGINFILE@@/intro.txt">link</a>','introformat' => FORMAT_HTML,'markingworkflow' => 1,'markingallocation' => 1,'blindmarking' => 1,'markinganonymous' => 1,'activityeditor' => ['text' => 'Test activity','format' => 1,],));// Add a file as assignment attachment.$context = \context_module::instance($assign1->cmid);$filerecord = array('component' => 'mod_assign', 'filearea' => 'intro', 'contextid' => $context->id, 'itemid' => 0,'filename' => 'intro.txt', 'filepath' => '/');$fs = get_file_storage();$fs->create_file_from_string($filerecord, 'Test intro file');// Create manual enrolment record.$enrolid = $DB->insert_record('enrol', (object)array('enrol' => 'manual','status' => 0,'courseid' => $course1->id));// Create the user and give them capabilities.$context = \context_course::instance($course1->id);$roleid = $this->assignUserCapability('moodle/course:view', $context->id);$context = \context_module::instance($assign1->cmid);$this->assignUserCapability('mod/assign:view', $context->id, $roleid);// Create the user enrolment record.$DB->insert_record('user_enrolments', (object)array('status' => 0,'enrolid' => $enrolid,'userid' => $USER->id));// Add a file as assignment attachment.$filerecord = array('component' => 'mod_assign', 'filearea' => ASSIGN_INTROATTACHMENT_FILEAREA,'contextid' => $context->id, 'itemid' => 0,'filename' => 'introattachment.txt', 'filepath' => '/');$fs = get_file_storage();$fs->create_file_from_string($filerecord, 'Test intro attachment file');$result = mod_assign_external::get_assignments();// We need to execute the return values cleaning process to simulate the web service server.$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);// Check the course and assignment are returned.$this->assertEquals(1, count($result['courses']));$course = $result['courses'][0];$this->assertEquals('Lightwork Course 1', $course['fullname']);$this->assertEquals('Lightwork Course 1', $course['shortname']);$this->assertEquals(1, count($course['assignments']));$assignment = $course['assignments'][0];$this->assertEquals($assign1->id, $assignment['id']);$this->assertEquals($course1->id, $assignment['course']);$this->assertEquals('English', $assignment['name']);$this->assertStringContainsString('the assignment intro text here', $assignment['intro']);$this->assertNotEmpty($assignment['configs']);// Check the url of the file attatched.$this->assertMatchesRegularExpression('@"' . $CFG->wwwroot . '/webservice/pluginfile.php/\d+/mod_assign/intro/intro\.txt"@', $assignment['intro']);$this->assertEquals(1, $assignment['markingworkflow']);$this->assertEquals(1, $assignment['markingallocation']);$this->assertEquals(1, $assignment['blindmarking']);$this->assertEquals(1, $assignment['markinganonymous']);$this->assertEquals(0, $assignment['preventsubmissionnotingroup']);$this->assertEquals(0, $assignment['timelimit']);$this->assertEquals(0, $assignment['submissionattachments']);$this->assertEquals('Test activity', $assignment['activity']);$this->assertEquals(1, $assignment['activityformat']);$this->assertEmpty($assignment['activityattachments']);$this->assertCount(1, $assignment['introattachments']);$this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);// Now, hide the descritption until the submission from date.$DB->set_field('assign', 'alwaysshowdescription', 0, array('id' => $assign1->id));$DB->set_field('assign', 'allowsubmissionsfromdate', time() + DAYSECS, array('id' => $assign1->id));$result = mod_assign_external::get_assignments(array($course1->id));// We need to execute the return values cleaning process to simulate the web service server.$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);$this->assertEquals(1, count($result['courses']));$course = $result['courses'][0];$this->assertEquals('Lightwork Course 1', $course['fullname']);$this->assertEquals(1, count($course['assignments']));$assignment = $course['assignments'][0];$this->assertEquals($assign1->id, $assignment['id']);$this->assertEquals($course1->id, $assignment['course']);$this->assertEquals('English', $assignment['name']);$this->assertArrayNotHasKey('intro', $assignment);$this->assertArrayNotHasKey('introattachments', $assignment);$this->assertEquals(1, $assignment['markingworkflow']);$this->assertEquals(1, $assignment['markingallocation']);$this->assertEquals(1, $assignment['blindmarking']);$this->assertEquals(1, $assignment['markinganonymous']);$this->assertEquals(0, $assignment['preventsubmissionnotingroup']);$result = mod_assign_external::get_assignments(array($course2->id));// We need to execute the return values cleaning process to simulate the web service server.$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);$this->assertEquals(0, count($result['courses']));$this->assertEquals(1, count($result['warnings']));// Test with non-enrolled user, but with view capabilities.$this->setAdminUser();$result = mod_assign_external::get_assignments();$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);$this->assertEquals(0, count($result['courses']));$this->assertEquals(0, count($result['warnings']));// Expect no courses, because we are not using the special flag.$result = mod_assign_external::get_assignments(array($course1->id));$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);$this->assertCount(0, $result['courses']);// Now use the special flag to return courses where you are not enroled in.$result = mod_assign_external::get_assignments(array($course1->id), array(), true);$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);$this->assertCount(1, $result['courses']);$course = $result['courses'][0];$this->assertEquals('Lightwork Course 1', $course['fullname']);$this->assertEquals(1, count($course['assignments']));$assignment = $course['assignments'][0];$this->assertEquals($assign1->id, $assignment['id']);$this->assertEquals($course1->id, $assignment['course']);$this->assertEquals('English', $assignment['name']);$this->assertArrayNotHasKey('intro', $assignment);$this->assertArrayNotHasKey('introattachments', $assignment);$this->assertEquals(1, $assignment['markingworkflow']);$this->assertEquals(1, $assignment['markingallocation']);$this->assertEquals(1, $assignment['blindmarking']);$this->assertEquals(1, $assignment['markinganonymous']);$this->assertEquals(0, $assignment['preventsubmissionnotingroup']);}/*** Test get_assignments with submissionstatement.*/public function test_get_assignments_with_submissionstatement(): void {global $DB, $USER, $CFG;$this->resetAfterTest(true);// Setup test data. Create 2 assigns, one with requiresubmissionstatement and the other without it.$course = $this->getDataGenerator()->create_course();$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id,'requiresubmissionstatement' => 1));$assign2 = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));// Create student.$student = self::getDataGenerator()->create_user();// Users enrolments.$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');// Update the submissionstatement.$submissionstatement = 'This is a fake submission statement.';set_config('submissionstatement', $submissionstatement, 'assign');$this->setUser($student);$result = mod_assign_external::get_assignments();// We need to execute the return values cleaning process to simulate the web service server.$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);// Check that the amount of courses and assignments is right.$this->assertCount(1, $result['courses']);$assignmentsret = $result['courses'][0]['assignments'];$this->assertCount(2, $assignmentsret);// Order the returned assignments by ID.usort($assignmentsret, function($a, $b) {return strcmp($a['id'], $b['id']);});// Check that the first assign contains the submission statement.$assignmentret = $assignmentsret[0];$this->assertEquals($assign->id, $assignmentret['id']);$this->assertEquals(1, $assignmentret['requiresubmissionstatement']);$this->assertEquals($submissionstatement, $assignmentret['submissionstatement']);// Check that the second assign does NOT contain the submission statement.$assignmentret = $assignmentsret[1];$this->assertEquals($assign2->id, $assignmentret['id']);$this->assertEquals(0, $assignmentret['requiresubmissionstatement']);$this->assertArrayNotHasKey('submissionstatement', $assignmentret);}/*** Test that get_assignments does not return intro attachments if submissionattachments enabled and there is no open submission.** @covers \mod_assign_external::get_assignments*/public function test_get_assignments_when_submissionattachments_is_enabled(): void {global $DB;$this->resetAfterTest(true);// Create data.$course1 = $this->getDataGenerator()->create_course();$user = $this->getDataGenerator()->create_user();$this->setUser($user);// First set submissionattachments to empty.$assign = self::getDataGenerator()->create_module('assign', array('course' => $course1->id,'name' => 'Test assignment','intro' => 'The assignment intro text here','introformat' => FORMAT_HTML,'submissionattachments' => 0,));$context = \context_module::instance($assign->cmid);// Enrol user as student.$this->getDataGenerator()->enrol_user($user->id, $course1->id, 'student');// Add a file as assignment attachment.$filerecord = array('component' => 'mod_assign', 'filearea' => ASSIGN_INTROATTACHMENT_FILEAREA,'contextid' => $context->id, 'itemid' => 0,'filename' => 'introattachment.txt', 'filepath' => '/');$fs = get_file_storage();$fs->create_file_from_string($filerecord, 'Test intro attachment file');// We need to execute the return values cleaning process to simulate the web service server.$result = mod_assign_external::get_assignments();$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);$this->assertEquals(1, count($result['courses']));$course = $result['courses'][0];$this->assertEquals(1, count($course['assignments']));$assignment = $course['assignments'][0];$this->assertCount(1, $assignment['introattachments']);$this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);// Now set submissionattachments to enabled. We will close assignment so assume intro attachments are not sent.$DB->set_field('assign', 'submissionattachments', '1', ['id' => $assignment['id']]);$DB->set_field('assign', 'cutoffdate', time() - 300, ['id' => $assignment['id']]);// We need to execute the return values cleaning process to simulate the web service server.$result = mod_assign_external::get_assignments();$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);$this->assertEquals(1, count($result['courses']));$course = $result['courses'][0];$this->assertEquals(1, count($course['assignments']));$assignment = $course['assignments'][0];$this->assertArrayNotHasKey('introattachments', $assignment);// Enrol a user as a teacher to override the setting.$this->getDataGenerator()->enrol_user($user->id, $course1->id, 'editingteacher');// We need to execute the return values cleaning process to simulate the web service server.$result = mod_assign_external::get_assignments();$result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);$this->assertEquals(1, count($result['courses']));$course = $result['courses'][0];$this->assertEquals(1, count($course['assignments']));$assignment = $course['assignments'][0];$this->assertCount(1, $assignment['introattachments']);$this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);}/*** Test get_submissions*/public function test_get_submissions(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment.$coursedata['idnumber'] = 'idnumbercourse1';$coursedata['fullname'] = 'Lightwork Course 1';$coursedata['summary'] = 'Lightwork Course 1 description';$coursedata['summaryformat'] = FORMAT_MOODLE;$course1 = self::getDataGenerator()->create_course($coursedata);$assigndata['course'] = $course1->id;$assigndata['name'] = 'lightwork assignment';$assign1 = self::getDataGenerator()->create_module('assign', $assigndata);// Create a student with an online text submission.// First attempt.$student = self::getDataGenerator()->create_user();$teacher = self::getDataGenerator()->create_user();$submission = new \stdClass();$submission->assignment = $assign1->id;$submission->userid = $student->id;$submission->timecreated = time();$submission->timemodified = $submission->timecreated;$submission->timestarted = $submission->timecreated;;$submission->status = 'draft';$submission->attemptnumber = 0;$submission->latest = 0;$sid = $DB->insert_record('assign_submission', $submission);// Second attempt.$now = time();$submission = new \stdClass();$submission->assignment = $assign1->id;$submission->userid = $student->id;$submission->timecreated = $now;$submission->timemodified = $now;$submission->timestarted = $now;$submission->status = 'submitted';$submission->attemptnumber = 1;$submission->latest = 1;$sid = $DB->insert_record('assign_submission', $submission);$submission->id = $sid;$onlinetextsubmission = new \stdClass();$onlinetextsubmission->onlinetext = "<p>online test text</p>";$onlinetextsubmission->onlineformat = 1;$onlinetextsubmission->submission = $submission->id;$onlinetextsubmission->assignment = $assign1->id;$DB->insert_record('assignsubmission_onlinetext', $onlinetextsubmission);// Enrol the teacher in the course.$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id);$this->setUser($teacher);$assignmentids[] = $assign1->id;$result = mod_assign_external::get_submissions($assignmentids);$result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);// Check the online text submission is NOT returned because the student is not yet enrolled in the course.$this->assertEquals(1, count($result['assignments']));$assignment = $result['assignments'][0];$this->assertEquals($assign1->id, $assignment['assignmentid']);$this->assertEquals(0, count($assignment['submissions']));// Enrol the student in the course.$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id);$result = mod_assign_external::get_submissions($assignmentids);$result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);$this->assertEquals(1, count($result['assignments']));$assignment = $result['assignments'][0];$this->assertEquals($assign1->id, $assignment['assignmentid']);// Now, we get the submission because the user is enrolled.$this->assertEquals(1, count($assignment['submissions']));$submission = $assignment['submissions'][0];$this->assertEquals($sid, $submission['id']);$this->assertCount(1, $submission['plugins']);$this->assertEquals('notgraded', $submission['gradingstatus']);$this->assertEquals($now, $submission['timestarted']);// Test locking the context.set_config('contextlocking', 1);$context = \context_course::instance($course1->id);$context->set_locked(true);$this->setUser($teacher);$assignmentids[] = $assign1->id;$result = mod_assign_external::get_submissions($assignmentids);$result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);$this->assertEquals(1, count($result['assignments']));}/*** Test get_submissions with teamsubmission enabled*/public function test_get_submissions_group_submission(): void {global $DB;$this->resetAfterTest(true);$result = $this->create_assign_with_student_and_teacher(array('assignsubmission_onlinetext_enabled' => 1,'teamsubmission' => 1));$assignmodule = $result['assign'];$student = $result['student'];$teacher = $result['teacher'];$course = $result['course'];$context = \context_course::instance($course->id);$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));$cm = get_coursemodule_from_instance('assign', $assignmodule->id);$context = \context_module::instance($cm->id);$assign = new mod_assign_testable_assign($context, $cm, $course);groups_add_member($group, $student);$this->setUser($student);$submission = $assign->get_group_submission($student->id, $group->id, true);$sid = $submission->id;$this->setUser($teacher);$assignmentids[] = $assignmodule->id;$result = mod_assign_external::get_submissions($assignmentids);$result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);$this->assertEquals(1, count($result['assignments']));$assignment = $result['assignments'][0];$this->assertEquals($assignmodule->id, $assignment['assignmentid']);$this->assertEquals(1, count($assignment['submissions']));$submission = $assignment['submissions'][0];$this->assertEquals($sid, $submission['id']);$this->assertEquals($group->id, $submission['groupid']);$this->assertEquals(0, $submission['userid']);}/*** Test get_submissions with teamsubmission enabled* and a group having a higher attemptnumber than another*/public function test_get_submissions_group_submission_attemptnumber(): void {global $DB;$this->resetAfterTest(true);$result = $this->create_assign_with_student_and_teacher(['assignsubmission_onlinetext_enabled' => 1,'attemptreopenmethod' => 'manual','teamsubmission' => 1,]);$assignmodule = $result['assign'];$course = $result['course'];$teacher = $result['teacher'];$student1 = $result['student'];$student2 = self::getDataGenerator()->create_user();// Enrol second user into the course.$studentrole = $DB->get_record('role', ['shortname' => 'student']);$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);$group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);groups_add_member($group1, $student1);$group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);groups_add_member($group2, $student2);$this->setUser($student1);mod_assign_external::save_submission($assignmodule->id,['onlinetext_editor' => ['text' => 'Group 1, Submission 1','format' => FORMAT_PLAIN,'itemid' => file_get_unused_draft_itemid(),]]);$this->setUser($student2);mod_assign_external::save_submission($assignmodule->id,['onlinetext_editor' => ['text' => 'Group 2, Submission 1','format' => FORMAT_PLAIN,'itemid' => file_get_unused_draft_itemid(),]]);mod_assign_external::submit_for_grading($assignmodule->id, 1);$this->setUser($teacher);mod_assign_external::save_grade($assignmodule->id, $student2->id, 0, -1, 1, "", 1);$this->setUser($student2);mod_assign_external::save_submission($assignmodule->id,['onlinetext_editor' => ['text' => 'Group 2, Submission 2','format' => FORMAT_PLAIN,'itemid' => file_get_unused_draft_itemid(),]]);$this->setUser($teacher);$result = mod_assign_external::get_submissions([$assignmodule->id]);$result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);$this->assertEquals(1, count($result['assignments']));[$assignment] = $result['assignments'];$this->assertEquals($assignmodule->id, $assignment['assignmentid']);$this->assertEquals(2, count($assignment['submissions']));$expectedsubmissions = ['Group 1, Submission 1', 'Group 2, Submission 2'];foreach ($assignment['submissions'] as $submission) {$this->assertContains($submission['plugins'][0]['editorfields'][0]['text'], $expectedsubmissions);}}/*** Test get_user_flags*/public function test_get_user_flags(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment.$coursedata['idnumber'] = 'idnumbercourse';$coursedata['fullname'] = 'Lightwork Course';$coursedata['summary'] = 'Lightwork Course description';$coursedata['summaryformat'] = FORMAT_MOODLE;$course = self::getDataGenerator()->create_course($coursedata);$assigndata['course'] = $course->id;$assigndata['name'] = 'lightwork assignment';$assign = self::getDataGenerator()->create_module('assign', $assigndata);// Create a manual enrolment record.$manualenroldata['enrol'] = 'manual';$manualenroldata['status'] = 0;$manualenroldata['courseid'] = $course->id;$enrolid = $DB->insert_record('enrol', $manualenroldata);// Create a teacher and give them capabilities.$context = \context_course::instance($course->id);$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);$context = \context_module::instance($assign->cmid);$this->assignUserCapability('mod/assign:grade', $context->id, $roleid);// Create the teacher's enrolment record.$userenrolmentdata['status'] = 0;$userenrolmentdata['enrolid'] = $enrolid;$userenrolmentdata['userid'] = $USER->id;$DB->insert_record('user_enrolments', $userenrolmentdata);// Create a student and give them a user flag record.$student = self::getDataGenerator()->create_user();$userflag = new \stdClass();$userflag->assignment = $assign->id;$userflag->userid = $student->id;$userflag->locked = 0;$userflag->mailed = 0;$userflag->extensionduedate = 0;$userflag->workflowstate = 'inmarking';$userflag->allocatedmarker = $USER->id;$DB->insert_record('assign_user_flags', $userflag);$assignmentids[] = $assign->id;$result = mod_assign_external::get_user_flags($assignmentids);// We need to execute the return values cleaning process to simulate the web service server.$result = external_api::clean_returnvalue(mod_assign_external::get_user_flags_returns(), $result);// Check that the correct user flag information for the student is returned.$this->assertEquals(1, count($result['assignments']));$assignment = $result['assignments'][0];$this->assertEquals($assign->id, $assignment['assignmentid']);// Should be one user flag record.$this->assertEquals(1, count($assignment['userflags']));$userflag = $assignment['userflags'][0];$this->assertEquals($student->id, $userflag['userid']);$this->assertEquals(0, $userflag['locked']);$this->assertEquals(0, $userflag['mailed']);$this->assertEquals(0, $userflag['extensionduedate']);$this->assertEquals('inmarking', $userflag['workflowstate']);$this->assertEquals($USER->id, $userflag['allocatedmarker']);}/*** Test get_user_mappings*/public function test_get_user_mappings(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment.$coursedata['idnumber'] = 'idnumbercourse';$coursedata['fullname'] = 'Lightwork Course';$coursedata['summary'] = 'Lightwork Course description';$coursedata['summaryformat'] = FORMAT_MOODLE;$course = self::getDataGenerator()->create_course($coursedata);$assigndata['course'] = $course->id;$assigndata['name'] = 'lightwork assignment';$assign = self::getDataGenerator()->create_module('assign', $assigndata);// Create a manual enrolment record.$manualenroldata['enrol'] = 'manual';$manualenroldata['status'] = 0;$manualenroldata['courseid'] = $course->id;$enrolid = $DB->insert_record('enrol', $manualenroldata);// Create a teacher and give them capabilities.$context = \context_course::instance($course->id);$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);$context = \context_module::instance($assign->cmid);$this->assignUserCapability('mod/assign:revealidentities', $context->id, $roleid);// Create the teacher's enrolment record.$userenrolmentdata['status'] = 0;$userenrolmentdata['enrolid'] = $enrolid;$userenrolmentdata['userid'] = $USER->id;$DB->insert_record('user_enrolments', $userenrolmentdata);// Create a student and give them a user mapping record.$student = self::getDataGenerator()->create_user();$mapping = new \stdClass();$mapping->assignment = $assign->id;$mapping->userid = $student->id;$DB->insert_record('assign_user_mapping', $mapping);$assignmentids[] = $assign->id;$result = mod_assign_external::get_user_mappings($assignmentids);// We need to execute the return values cleaning process to simulate the web service server.$result = external_api::clean_returnvalue(mod_assign_external::get_user_mappings_returns(), $result);// Check that the correct user mapping information for the student is returned.$this->assertEquals(1, count($result['assignments']));$assignment = $result['assignments'][0];$this->assertEquals($assign->id, $assignment['assignmentid']);// Should be one user mapping record.$this->assertEquals(1, count($assignment['mappings']));$mapping = $assignment['mappings'][0];$this->assertEquals($student->id, $mapping['userid']);}/*** Test lock_submissions*/public function test_lock_submissions(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['assignsubmission_onlinetext_enabled'] = 1;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$student2 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);// Create a student1 with an online text submission.// Simulate a submission.$this->setUser($student1);$submission = $assign->get_user_submission($student1->id, true);$data = new \stdClass();$data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),'text' => 'Submission text','format' => FORMAT_MOODLE);$plugin = $assign->get_submission_plugin_by_type('onlinetext');$plugin->save($submission, $data);// Ready to test.$this->setUser($teacher);$students = array($student1->id, $student2->id);$result = mod_assign_external::lock_submissions($instance->id, $students);$result = external_api::clean_returnvalue(mod_assign_external::lock_submissions_returns(), $result);// Check for 0 warnings.$this->assertEquals(0, count($result));$this->setUser($student2);$submission = $assign->get_user_submission($student2->id, true);$data = new \stdClass();$data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),'text' => 'Submission text','format' => FORMAT_MOODLE);$notices = array();$this->expectException(\moodle_exception::class);$assign->save_submission($data, $notices);}/*** Test unlock_submissions*/public function test_unlock_submissions(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['assignsubmission_onlinetext_enabled'] = 1;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$student2 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);// Create a student1 with an online text submission.// Simulate a submission.$this->setUser($student1);$submission = $assign->get_user_submission($student1->id, true);$data = new \stdClass();$data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),'text' => 'Submission text','format' => FORMAT_MOODLE);$plugin = $assign->get_submission_plugin_by_type('onlinetext');$plugin->save($submission, $data);// Ready to test.$this->setUser($teacher);$students = array($student1->id, $student2->id);$result = mod_assign_external::lock_submissions($instance->id, $students);$result = external_api::clean_returnvalue(mod_assign_external::lock_submissions_returns(), $result);// Check for 0 warnings.$this->assertEquals(0, count($result));$result = mod_assign_external::unlock_submissions($instance->id, $students);$result = external_api::clean_returnvalue(mod_assign_external::unlock_submissions_returns(), $result);// Check for 0 warnings.$this->assertEquals(0, count($result));$this->setUser($student2);$submission = $assign->get_user_submission($student2->id, true);$data = new \stdClass();$data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),'text' => 'Submission text','format' => FORMAT_MOODLE);$notices = array();$assign->save_submission($data, $notices);}/*** Test submit_for_grading*/public function test_submit_for_grading(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();set_config('submissionreceipts', 0, 'assign');$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['assignsubmission_onlinetext_enabled'] = 1;$params['submissiondrafts'] = 1;$params['sendnotifications'] = 0;$params['requiresubmissionstatement'] = 1;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);// Create a student1 with an online text submission.// Simulate a submission.$this->setUser($student1);$submission = $assign->get_user_submission($student1->id, true);$data = new \stdClass();$data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),'text' => 'Submission text','format' => FORMAT_MOODLE);$plugin = $assign->get_submission_plugin_by_type('onlinetext');$plugin->save($submission, $data);$result = mod_assign_external::submit_for_grading($instance->id, false);$result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);// Should be 1 fail because the submission statement was not aceptted.$this->assertEquals(1, count($result));$result = mod_assign_external::submit_for_grading($instance->id, true);$result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);// Check for 0 warnings.$this->assertEquals(0, count($result));$submission = $assign->get_user_submission($student1->id, false);$this->assertEquals(ASSIGN_SUBMISSION_STATUS_SUBMITTED, $submission->status);}/*** Test save_user_extensions*/public function test_save_user_extensions(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);$this->setUser($teacher);$now = time();$yesterday = $now - 24 * 60 * 60;$tomorrow = $now + 24 * 60 * 60;set_config('submissionreceipts', 0, 'assign');$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['submissiondrafts'] = 1;$params['sendnotifications'] = 0;$params['duedate'] = $yesterday;$params['cutoffdate'] = $now - 10;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);$this->setUser($student1);$result = mod_assign_external::submit_for_grading($instance->id, true);$result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);// Check for 0 warnings.$this->assertEquals(1, count($result));$this->setUser($teacher);$result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));$result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);$this->assertEquals(1, count($result));$this->setUser($teacher);$result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($yesterday - 10));$result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);$this->assertEquals(1, count($result));$this->setUser($teacher);$result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($tomorrow));$result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);$this->assertEquals(0, count($result));$this->setUser($student1);$result = mod_assign_external::submit_for_grading($instance->id, true);$result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);$this->assertEquals(0, count($result));$this->setUser($student1);$result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));$result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);}/*** Test reveal_identities*/public function test_reveal_identities(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);$this->setUser($teacher);$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['submissiondrafts'] = 1;$params['sendnotifications'] = 0;$params['blindmarking'] = 1;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);$this->setUser($student1);$this->expectException(\required_capability_exception::class);$result = mod_assign_external::reveal_identities($instance->id);$result = external_api::clean_returnvalue(mod_assign_external::reveal_identities_returns(), $result);$this->assertEquals(1, count($result));$this->assertEquals(true, $assign->is_blind_marking());$this->setUser($teacher);$result = mod_assign_external::reveal_identities($instance->id);$result = external_api::clean_returnvalue(mod_assign_external::reveal_identities_returns(), $result);$this->assertEquals(0, count($result));$this->assertEquals(false, $assign->is_blind_marking());$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['submissiondrafts'] = 1;$params['sendnotifications'] = 0;$params['blindmarking'] = 0;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$result = mod_assign_external::reveal_identities($instance->id);$result = external_api::clean_returnvalue(mod_assign_external::reveal_identities_returns(), $result);$this->assertEquals(1, count($result));$this->assertEquals(false, $assign->is_blind_marking());}/*** Test revert_submissions_to_draft*/public function test_revert_submissions_to_draft(): void {global $DB, $USER;$this->resetAfterTest(true);set_config('submissionreceipts', 0, 'assign');// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['sendnotifications'] = 0;$params['submissiondrafts'] = 1;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$student2 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);// Create a student1 with an online text submission.// Simulate a submission.$this->setUser($student1);$result = mod_assign_external::submit_for_grading($instance->id, true);$result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);$this->assertEquals(0, count($result));// Ready to test.$this->setUser($teacher);$students = array($student1->id, $student2->id);$result = mod_assign_external::revert_submissions_to_draft($instance->id, array($student1->id));$result = external_api::clean_returnvalue(mod_assign_external::revert_submissions_to_draft_returns(), $result);// Check for 0 warnings.$this->assertEquals(0, count($result));}/*** Test save_submission*/public function test_save_submission(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);$this->setUser($teacher);$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['assignsubmission_onlinetext_enabled'] = 1;$params['assignsubmission_file_enabled'] = 1;$params['assignsubmission_file_maxfiles'] = 5;$params['assignsubmission_file_maxsizebytes'] = 1024 * 1024;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$student2 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);// Create a student1 with an online text submission.// Simulate a submission.$this->setUser($student1);// Create a file in a draft area.$draftidfile = file_get_unused_draft_itemid();$usercontext = \context_user::instance($student1->id);$filerecord = array('contextid' => $usercontext->id,'component' => 'user','filearea' => 'draft','itemid' => $draftidfile,'filepath' => '/','filename' => 'testtext.txt',);$fs = get_file_storage();$fs->create_file_from_string($filerecord, 'text contents');// Create another file in a different draft area.$draftidonlinetext = file_get_unused_draft_itemid();$filerecord = array('contextid' => $usercontext->id,'component' => 'user','filearea' => 'draft','itemid' => $draftidonlinetext,'filepath' => '/','filename' => 'shouldbeanimage.txt',);$fs->create_file_from_string($filerecord, 'image contents (not really)');// Now try a submission.$submissionpluginparams = array();$submissionpluginparams['files_filemanager'] = $draftidfile;$onlinetexteditorparams = array('text' => '<p>Yeeha!</p>','format' => 1,'itemid' => $draftidonlinetext);$submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;$result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);$result = external_api::clean_returnvalue(mod_assign_external::save_submission_returns(), $result);$this->assertEquals(0, count($result));// Set up a due and cutoff passed date.$instance->duedate = time() - WEEKSECS;$instance->cutoffdate = time() - WEEKSECS;$DB->update_record('assign', $instance);$result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);$result = external_api::clean_returnvalue(mod_assign_external::save_submission_returns(), $result);$this->assertCount(1, $result);$this->assertEquals(get_string('duedatereached', 'assign'), $result[0]['item']);}/*** Test save_grade*/public function test_save_grade(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);$this->setUser($teacher);$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['assignfeedback_file_enabled'] = 1;$params['assignfeedback_comments_enabled'] = 1;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$student2 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id,$course->id,$studentrole->id);$this->getDataGenerator()->enrol_user($student2->id,$course->id,$studentrole->id);// Simulate a grade.$this->setUser($teacher);// Create a file in a draft area.$draftidfile = file_get_unused_draft_itemid();$usercontext = \context_user::instance($teacher->id);$filerecord = array('contextid' => $usercontext->id,'component' => 'user','filearea' => 'draft','itemid' => $draftidfile,'filepath' => '/','filename' => 'testtext.txt',);$fs = get_file_storage();$fs->create_file_from_string($filerecord, 'text contents');// Now try a grade.$feedbackpluginparams = array();$feedbackpluginparams['files_filemanager'] = $draftidfile;$feedbackeditorparams = array('text' => 'Yeeha!','format' => 1);$feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;$result = mod_assign_external::save_grade($instance->id,$student1->id,50.0,-1,true,'released',false,$feedbackpluginparams);// No warnings.$this->assertNull($result);$result = mod_assign_external::get_grades(array($instance->id));$result = external_api::clean_returnvalue(mod_assign_external::get_grades_returns(), $result);$this->assertEquals((float)$result['assignments'][0]['grades'][0]['grade'], '50.0');}/*** Test save grades with advanced grading data*/public function test_save_grades_with_advanced_grading(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['assignfeedback_file_enabled'] = 0;$params['assignfeedback_comments_enabled'] = 0;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$student2 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);$this->setUser($teacher);$feedbackpluginparams = array();$feedbackpluginparams['files_filemanager'] = 0;$feedbackeditorparams = array('text' => '', 'format' => 1);$feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;// Create advanced grading data.// Create grading area.$gradingarea = array('contextid' => $context->id,'component' => 'mod_assign','areaname' => 'submissions','activemethod' => 'rubric');$areaid = $DB->insert_record('grading_areas', $gradingarea);// Create a rubric grading definition.$rubricdefinition = array ('areaid' => $areaid,'method' => 'rubric','name' => 'test','status' => 20,'copiedfromid' => 1,'timecreated' => 1,'usercreated' => $teacher->id,'timemodified' => 1,'usermodified' => $teacher->id,'timecopied' => 0);$definitionid = $DB->insert_record('grading_definitions', $rubricdefinition);// Create a criterion with a level.$rubriccriteria = array ('definitionid' => $definitionid,'sortorder' => 1,'description' => 'Demonstrate an understanding of disease control','descriptionformat' => 0);$criterionid = $DB->insert_record('gradingform_rubric_criteria', $rubriccriteria);$rubriclevel1 = array ('criterionid' => $criterionid,'score' => 50,'definition' => 'pass','definitionformat' => 0);$rubriclevel2 = array ('criterionid' => $criterionid,'score' => 100,'definition' => 'excellent','definitionformat' => 0);$rubriclevel3 = array ('criterionid' => $criterionid,'score' => 0,'definition' => 'fail','definitionformat' => 0);$levelid1 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel1);$levelid2 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel2);$levelid3 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel3);// Create the filling.$student1filling = array ('criterionid' => $criterionid,'levelid' => $levelid1,'remark' => 'well done you passed','remarkformat' => 0);$student2filling = array ('criterionid' => $criterionid,'levelid' => $levelid2,'remark' => 'Excellent work','remarkformat' => 0);$student1criteria = array(array('criterionid' => $criterionid, 'fillings' => array($student1filling)));$student1advancedgradingdata = array('rubric' => array('criteria' => $student1criteria));$student2criteria = array(array('criterionid' => $criterionid, 'fillings' => array($student2filling)));$student2advancedgradingdata = array('rubric' => array('criteria' => $student2criteria));$grades = array();$student1gradeinfo = array();$student1gradeinfo['userid'] = $student1->id;$student1gradeinfo['grade'] = 0; // Ignored since advanced grading is being used.$student1gradeinfo['attemptnumber'] = -1;$student1gradeinfo['addattempt'] = true;$student1gradeinfo['workflowstate'] = 'released';$student1gradeinfo['plugindata'] = $feedbackpluginparams;$student1gradeinfo['advancedgradingdata'] = $student1advancedgradingdata;$grades[] = $student1gradeinfo;$student2gradeinfo = array();$student2gradeinfo['userid'] = $student2->id;$student2gradeinfo['grade'] = 0; // Ignored since advanced grading is being used.$student2gradeinfo['attemptnumber'] = -1;$student2gradeinfo['addattempt'] = true;$student2gradeinfo['workflowstate'] = 'released';$student2gradeinfo['plugindata'] = $feedbackpluginparams;$student2gradeinfo['advancedgradingdata'] = $student2advancedgradingdata;$grades[] = $student2gradeinfo;$result = mod_assign_external::save_grades($instance->id, false, $grades);$this->assertNull($result);$student1grade = $DB->get_record('assign_grades',array('userid' => $student1->id, 'assignment' => $instance->id),'*',MUST_EXIST);$this->assertEquals((float)$student1grade->grade, '50.0');$student2grade = $DB->get_record('assign_grades',array('userid' => $student2->id, 'assignment' => $instance->id),'*',MUST_EXIST);$this->assertEquals((float)$student2grade->grade, '100.0');}/*** Test save grades for a team submission*/public function test_save_grades_with_group_submission(): void {global $DB, $USER, $CFG;require_once($CFG->dirroot . '/group/lib.php');$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);$groupingdata = array();$groupingdata['courseid'] = $course->id;$groupingdata['name'] = 'Group assignment grouping';$grouping = self::getDataGenerator()->create_grouping($groupingdata);$group1data = array();$group1data['courseid'] = $course->id;$group1data['name'] = 'Team 1';$group2data = array();$group2data['courseid'] = $course->id;$group2data['name'] = 'Team 2';$group1 = self::getDataGenerator()->create_group($group1data);$group2 = self::getDataGenerator()->create_group($group2data);groups_assign_grouping($grouping->id, $group1->id);groups_assign_grouping($grouping->id, $group2->id);$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['teamsubmission'] = 1;$params['teamsubmissiongroupingid'] = $grouping->id;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$student2 = self::getDataGenerator()->create_user();$student3 = self::getDataGenerator()->create_user();$student4 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);$this->getDataGenerator()->enrol_user($student3->id, $course->id, $studentrole->id);$this->getDataGenerator()->enrol_user($student4->id, $course->id, $studentrole->id);groups_add_member($group1->id, $student1->id);groups_add_member($group1->id, $student2->id);groups_add_member($group1->id, $student3->id);groups_add_member($group2->id, $student4->id);$this->setUser($teacher);$feedbackpluginparams = array();$feedbackpluginparams['files_filemanager'] = 0;$feedbackeditorparams = array('text' => '', 'format' => 1);$feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;$grades1 = array();$student1gradeinfo = array();$student1gradeinfo['userid'] = $student1->id;$student1gradeinfo['grade'] = 50;$student1gradeinfo['attemptnumber'] = -1;$student1gradeinfo['addattempt'] = true;$student1gradeinfo['workflowstate'] = 'released';$student1gradeinfo['plugindata'] = $feedbackpluginparams;$grades1[] = $student1gradeinfo;$student2gradeinfo = array();$student2gradeinfo['userid'] = $student2->id;$student2gradeinfo['grade'] = 75;$student2gradeinfo['attemptnumber'] = -1;$student2gradeinfo['addattempt'] = true;$student2gradeinfo['workflowstate'] = 'released';$student2gradeinfo['plugindata'] = $feedbackpluginparams;$grades1[] = $student2gradeinfo;// Expect an exception since 2 grades have been submitted for the same team.$this->expectException(\invalid_parameter_exception::class);$result = mod_assign_external::save_grades($instance->id, true, $grades1);$result = external_api::clean_returnvalue(mod_assign_external::save_grades_returns(), $result);$grades2 = array();$student3gradeinfo = array();$student3gradeinfo['userid'] = $student3->id;$student3gradeinfo['grade'] = 50;$student3gradeinfo['attemptnumber'] = -1;$student3gradeinfo['addattempt'] = true;$student3gradeinfo['workflowstate'] = 'released';$student3gradeinfo['plugindata'] = $feedbackpluginparams;$grades2[] = $student3gradeinfo;$student4gradeinfo = array();$student4gradeinfo['userid'] = $student4->id;$student4gradeinfo['grade'] = 75;$student4gradeinfo['attemptnumber'] = -1;$student4gradeinfo['addattempt'] = true;$student4gradeinfo['workflowstate'] = 'released';$student4gradeinfo['plugindata'] = $feedbackpluginparams;$grades2[] = $student4gradeinfo;$result = mod_assign_external::save_grades($instance->id, true, $grades2);$result = external_api::clean_returnvalue(mod_assign_external::save_grades_returns(), $result);// There should be no warnings.$this->assertEquals(0, count($result));$student3grade = $DB->get_record('assign_grades',array('userid' => $student3->id, 'assignment' => $instance->id),'*',MUST_EXIST);$this->assertEquals($student3grade->grade, '50.0');$student4grade = $DB->get_record('assign_grades',array('userid' => $student4->id, 'assignment' => $instance->id),'*',MUST_EXIST);$this->assertEquals($student4grade->grade, '75.0');}/*** Test copy_previous_attempt*/public function test_copy_previous_attempt(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment and users.$course = self::getDataGenerator()->create_course();$teacher = self::getDataGenerator()->create_user();$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);$this->setUser($teacher);$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$params['course'] = $course->id;$params['assignsubmission_onlinetext_enabled'] = 1;$params['assignsubmission_file_enabled'] = 0;$params['assignfeedback_file_enabled'] = 0;$params['attemptreopenmethod'] = 'manual';$params['maxattempts'] = 5;$instance = $generator->create_instance($params);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$student1 = self::getDataGenerator()->create_user();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);// Now try a submission.$this->setUser($student1);$draftidonlinetext = file_get_unused_draft_itemid();$submissionpluginparams = array();$onlinetexteditorparams = array('text' => 'Yeeha!', 'format' => 1, 'itemid' => $draftidonlinetext);$submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;$submissionpluginparams['files_filemanager'] = file_get_unused_draft_itemid();$result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);$result = external_api::clean_returnvalue(mod_assign_external::save_submission_returns(), $result);$this->setUser($teacher);// Add a grade and reopen the attempt.// Now try a grade.$feedbackpluginparams = array();$feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();$feedbackeditorparams = array('text' => 'Yeeha!', 'format' => 1);$feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;$result = mod_assign_external::save_grade($instance->id,$student1->id,50.0,-1,true,'released',false,$feedbackpluginparams);$this->assertNull($result);$this->setUser($student1);// Now copy the previous attempt.$result = mod_assign_external::copy_previous_attempt($instance->id);$result = external_api::clean_returnvalue(mod_assign_external::copy_previous_attempt_returns(), $result);// No warnings.$this->assertEquals(0, count($result));$this->setUser($teacher);$result = mod_assign_external::get_submissions(array($instance->id));$result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);// Check we are now on the second attempt.$this->assertEquals($result['assignments'][0]['submissions'][0]['attemptnumber'], 1);// Check the plugins data is not empty.$this->assertNotEmpty($result['assignments'][0]['submissions'][0]['plugins']);}/*** Test set_user_flags*/public function test_set_user_flags(): void {global $DB, $USER;$this->resetAfterTest(true);// Create a course and assignment.$coursedata['idnumber'] = 'idnumbercourse';$coursedata['fullname'] = 'Lightwork Course';$coursedata['summary'] = 'Lightwork Course description';$coursedata['summaryformat'] = FORMAT_MOODLE;$course = self::getDataGenerator()->create_course($coursedata);$assigndata['course'] = $course->id;$assigndata['name'] = 'lightwork assignment';$assign = self::getDataGenerator()->create_module('assign', $assigndata);// Create a manual enrolment record.$manualenroldata['enrol'] = 'manual';$manualenroldata['status'] = 0;$manualenroldata['courseid'] = $course->id;$enrolid = $DB->insert_record('enrol', $manualenroldata);// Create a teacher and give them capabilities.$context = \context_course::instance($course->id);$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);$context = \context_module::instance($assign->cmid);$this->assignUserCapability('mod/assign:grade', $context->id, $roleid);// Create the teacher's enrolment record.$userenrolmentdata['status'] = 0;$userenrolmentdata['enrolid'] = $enrolid;$userenrolmentdata['userid'] = $USER->id;$DB->insert_record('user_enrolments', $userenrolmentdata);// Create a student.$student = self::getDataGenerator()->create_user();// Create test user flags record.$userflags = array();$userflag['userid'] = $student->id;$userflag['workflowstate'] = 'inmarking';$userflag['allocatedmarker'] = $USER->id;$userflags = array($userflag);$createduserflags = mod_assign_external::set_user_flags($assign->id, $userflags);// We need to execute the return values cleaning process to simulate the web service server.$createduserflags = external_api::clean_returnvalue(mod_assign_external::set_user_flags_returns(), $createduserflags);$this->assertEquals($student->id, $createduserflags[0]['userid']);$createduserflag = $DB->get_record('assign_user_flags', array('id' => $createduserflags[0]['id']));// Confirm that all data was inserted correctly.$this->assertEquals($student->id, $createduserflag->userid);$this->assertEquals($assign->id, $createduserflag->assignment);$this->assertEquals(0, $createduserflag->locked);$this->assertEquals(2, $createduserflag->mailed);$this->assertEquals(0, $createduserflag->extensionduedate);$this->assertEquals('inmarking', $createduserflag->workflowstate);$this->assertEquals($USER->id, $createduserflag->allocatedmarker);// Create update data.$userflags = array();$userflag['userid'] = $createduserflag->userid;$userflag['workflowstate'] = 'readyforreview';$userflags = array($userflag);$updateduserflags = mod_assign_external::set_user_flags($assign->id, $userflags);// We need to execute the return values cleaning process to simulate the web service server.$updateduserflags = external_api::clean_returnvalue(mod_assign_external::set_user_flags_returns(), $updateduserflags);$this->assertEquals($student->id, $updateduserflags[0]['userid']);$updateduserflag = $DB->get_record('assign_user_flags', array('id' => $updateduserflags[0]['id']));// Confirm that all data was updated correctly.$this->assertEquals($student->id, $updateduserflag->userid);$this->assertEquals($assign->id, $updateduserflag->assignment);$this->assertEquals(0, $updateduserflag->locked);$this->assertEquals(2, $updateduserflag->mailed);$this->assertEquals(0, $updateduserflag->extensionduedate);$this->assertEquals('readyforreview', $updateduserflag->workflowstate);$this->assertEquals($USER->id, $updateduserflag->allocatedmarker);}/*** Test view_grading_table*/public function test_view_grading_table_invalid_instance(): void {global $DB;$this->resetAfterTest(true);// Setup test data.$course = $this->getDataGenerator()->create_course();$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));$context = \context_module::instance($assign->cmid);$cm = get_coursemodule_from_instance('assign', $assign->id);// Test invalid instance id.$this->expectException(\dml_missing_record_exception::class);mod_assign_external::view_grading_table(0);}/*** Test view_grading_table*/public function test_view_grading_table_not_enrolled(): void {global $DB;$this->resetAfterTest(true);// Setup test data.$course = $this->getDataGenerator()->create_course();$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));$context = \context_module::instance($assign->cmid);$cm = get_coursemodule_from_instance('assign', $assign->id);// Test not-enrolled user.$user = self::getDataGenerator()->create_user();$this->setUser($user);$this->expectException(\require_login_exception::class);mod_assign_external::view_grading_table($assign->id);}/*** Test view_grading_table*/public function test_view_grading_table_correct(): void {global $DB;$this->resetAfterTest(true);// Setup test data.$course = $this->getDataGenerator()->create_course();$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));$context = \context_module::instance($assign->cmid);$cm = get_coursemodule_from_instance('assign', $assign->id);// Test user with full capabilities.$user = self::getDataGenerator()->create_user();$this->setUser($user);$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);// Trigger and capture the event.$sink = $this->redirectEvents();$result = mod_assign_external::view_grading_table($assign->id);$result = external_api::clean_returnvalue(mod_assign_external::view_grading_table_returns(), $result);$events = $sink->get_events();$this->assertCount(1, $events);$event = array_shift($events);// Checking that the event contains the expected values.$this->assertInstanceOf('\mod_assign\event\grading_table_viewed', $event);$this->assertEquals($context, $event->get_context());$moodleurl = new \moodle_url('/mod/assign/view.php', array('id' => $cm->id));$this->assertEquals($moodleurl, $event->get_url());$this->assertEventContextNotUsed($event);$this->assertNotEmpty($event->get_name());}/*** Test view_grading_table*/public function test_view_grading_table_without_capability(): void {global $DB;$this->resetAfterTest(true);// Setup test data.$course = $this->getDataGenerator()->create_course();$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));$context = \context_module::instance($assign->cmid);$cm = get_coursemodule_from_instance('assign', $assign->id);// Test user with no capabilities.$user = self::getDataGenerator()->create_user();$this->setUser($user);$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);// We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));assign_capability('mod/assign:view', CAP_PROHIBIT, $teacherrole->id, $context->id);// Empty all the caches that may be affected by this change.accesslib_clear_all_caches_for_unit_testing();\course_modinfo::clear_instance_cache();$this->expectException(\require_login_exception::class);$this->expectExceptionMessage('Course or activity not accessible. (Activity is hidden)');mod_assign_external::view_grading_table($assign->id);}/*** Test subplugins availability*/public function test_subplugins_availability(): void {global $CFG;require_once($CFG->dirroot . '/mod/assign/adminlib.php');$this->resetAfterTest(true);// Hide assignment file submissiong plugin.$pluginmanager = new \assign_plugin_manager('assignsubmission');$pluginmanager->hide_plugin('file');$parameters = mod_assign_external::save_submission_parameters();$this->assertTrue(!isset($parameters->keys['plugindata']->keys['files_filemanager']));// Show it again and check that the value is returned as optional.$pluginmanager->show_plugin('file');$parameters = mod_assign_external::save_submission_parameters();$this->assertTrue(isset($parameters->keys['plugindata']->keys['files_filemanager']));$this->assertEquals(VALUE_OPTIONAL, $parameters->keys['plugindata']->keys['files_filemanager']->required);// Hide feedback file submissiong plugin.$pluginmanager = new \assign_plugin_manager('assignfeedback');$pluginmanager->hide_plugin('file');$parameters = mod_assign_external::save_grade_parameters();$this->assertTrue(!isset($parameters->keys['plugindata']->keys['files_filemanager']));// Show it again and check that the value is returned as optional.$pluginmanager->show_plugin('file');$parameters = mod_assign_external::save_grade_parameters();$this->assertTrue(isset($parameters->keys['plugindata']->keys['files_filemanager']));$this->assertEquals(VALUE_OPTIONAL, $parameters->keys['plugindata']->keys['files_filemanager']->required);// Check a different one.$pluginmanager->show_plugin('comments');$this->assertTrue(isset($parameters->keys['plugindata']->keys['assignfeedbackcomments_editor']));$this->assertEquals(VALUE_OPTIONAL, $parameters->keys['plugindata']->keys['assignfeedbackcomments_editor']->required);}/*** Test test_view_submission_status*/public function test_view_submission_status(): void {global $DB;$this->resetAfterTest(true);$this->setAdminUser();// Setup test data.$course = $this->getDataGenerator()->create_course();$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));$context = \context_module::instance($assign->cmid);$cm = get_coursemodule_from_instance('assign', $assign->id);// Test invalid instance id.try {mod_assign_external::view_submission_status(0);$this->fail('Exception expected due to invalid mod_assign instance id.');} catch (\moodle_exception $e) {$this->assertEquals('invalidrecord', $e->errorcode);}// Test not-enrolled user.$user = self::getDataGenerator()->create_user();$this->setUser($user);try {mod_assign_external::view_submission_status($assign->id);$this->fail('Exception expected due to not enrolled user.');} catch (\moodle_exception $e) {$this->assertEquals('requireloginerror', $e->errorcode);}// Test user with full capabilities.$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);// Trigger and capture the event.$sink = $this->redirectEvents();$result = mod_assign_external::view_submission_status($assign->id);$result = external_api::clean_returnvalue(mod_assign_external::view_submission_status_returns(), $result);$events = $sink->get_events();$this->assertCount(1, $events);$event = array_shift($events);// Checking that the event contains the expected values.$this->assertInstanceOf('\mod_assign\event\submission_status_viewed', $event);$this->assertEquals($context, $event->get_context());$moodleurl = new \moodle_url('/mod/assign/view.php', array('id' => $cm->id));$this->assertEquals($moodleurl, $event->get_url());$this->assertEventContextNotUsed($event);$this->assertNotEmpty($event->get_name());// Test user with no capabilities.// We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.assign_capability('mod/assign:view', CAP_PROHIBIT, $studentrole->id, $context->id);accesslib_clear_all_caches_for_unit_testing();\course_modinfo::clear_instance_cache();try {mod_assign_external::view_submission_status($assign->id);$this->fail('Exception expected due to missing capability.');} catch (\moodle_exception $e) {$this->assertEquals('requireloginerror', $e->errorcode);}}/*** Test get_submission_status for a draft submission.*/public function test_get_submission_status_in_draft_status(): void {$this->resetAfterTest(true);list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();$studentsubmission = $assign->get_user_submission($student1->id, true);$result = mod_assign_external::get_submission_status($assign->get_instance()->id);// We expect debugging because of the $PAGE object, this won't happen in a normal WS request.$this->assertDebuggingCalled();$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);// The submission is now in draft mode.$this->assertCount(0, $result['warnings']);$this->assertFalse(isset($result['gradingsummary']));$this->assertFalse(isset($result['feedback']));$this->assertFalse(isset($result['previousattempts']));$this->assertTrue($result['lastattempt']['submissionsenabled']);$this->assertTrue($result['lastattempt']['canedit']);$this->assertTrue($result['lastattempt']['cansubmit']);$this->assertFalse($result['lastattempt']['locked']);$this->assertFalse($result['lastattempt']['graded']);$this->assertEmpty($result['lastattempt']['extensionduedate']);$this->assertFalse($result['lastattempt']['blindmarking']);$this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);$this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);$this->assertEquals($student1->id, $result['lastattempt']['submission']['userid']);$this->assertEquals(0, $result['lastattempt']['submission']['attemptnumber']);$this->assertEquals('draft', $result['lastattempt']['submission']['status']);$this->assertEquals(0, $result['lastattempt']['submission']['groupid']);$this->assertEquals($assign->get_instance()->id, $result['lastattempt']['submission']['assignment']);$this->assertEquals(1, $result['lastattempt']['submission']['latest']);// Map plugins based on their type - we can't rely on them being in a// particular order, especially if 3rd party plugins are installed.$submissionplugins = array();foreach ($result['lastattempt']['submission']['plugins'] as $plugin) {$submissionplugins[$plugin['type']] = $plugin;}// Format expected online text.$onlinetext = 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>';list($expectedtext,$expectedformat) = \core_external\util::format_text($onlinetext,FORMAT_HTML,$assign->get_context(),'assignsubmission_onlinetext',ASSIGNSUBMISSION_ONLINETEXT_FILEAREA,$studentsubmission->id);$this->assertEquals($expectedtext, $submissionplugins['onlinetext']['editorfields'][0]['text']);$this->assertEquals($expectedformat, $submissionplugins['onlinetext']['editorfields'][0]['format']);$this->assertEquals('/', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);$this->assertEquals('t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filename']);// Test assignment data.$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);}/*** Test get_submission_status for a submitted submission.*/public function test_get_submission_status_in_submission_status(): void {$this->resetAfterTest(true);list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);$result = mod_assign_external::get_submission_status($assign->get_instance()->id);// We expect debugging because of the $PAGE object, this won't happen in a normal WS request.$this->assertDebuggingCalled();$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertCount(0, $result['warnings']);$this->assertFalse(isset($result['gradingsummary']));$this->assertFalse(isset($result['feedback']));$this->assertFalse(isset($result['previousattempts']));$this->assertTrue($result['lastattempt']['submissionsenabled']);$this->assertFalse($result['lastattempt']['canedit']);$this->assertFalse($result['lastattempt']['cansubmit']);$this->assertFalse($result['lastattempt']['locked']);$this->assertFalse($result['lastattempt']['graded']);$this->assertEmpty($result['lastattempt']['extensionduedate']);$this->assertFalse($result['lastattempt']['blindmarking']);$this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);$this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);$this->assertNull($result['lastattempt']['submission']['timestarted']);// Test assignment data.$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);}/*** Test get_submission_status using the teacher role.*/public function test_get_submission_status_in_submission_status_for_teacher(): void {global $DB;$this->resetAfterTest(true);list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);// Now, as teacher, see the grading summary.$this->setUser($teacher);// First one group.$result = mod_assign_external::get_submission_status($assign->get_instance()->id, 0, $g1->id);// We expect debugging because of the $PAGE object, this won't happen in a normal WS request.$this->assertDebuggingCalled();$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertCount(0, $result['warnings']);$this->assertFalse(isset($result['lastattempt']));$this->assertFalse(isset($result['feedback']));$this->assertFalse(isset($result['previousattempts']));$this->assertEquals(1, $result['gradingsummary']['participantcount']);$this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);$this->assertEquals(1, $result['gradingsummary']['submissionsenabled']);$this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);$this->assertEquals(1, $result['gradingsummary']['submissionssubmittedcount']); // One student from G1 submitted.$this->assertEquals(1, $result['gradingsummary']['submissionsneedgradingcount']); // One student from G1 submitted.$this->assertEmpty($result['gradingsummary']['warnofungroupedusers']);// Second group.$result = mod_assign_external::get_submission_status($assign->get_instance()->id, 0, $g2->id);$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertCount(0, $result['warnings']);$this->assertEquals(1, $result['gradingsummary']['participantcount']);$this->assertEquals(0, $result['gradingsummary']['submissionssubmittedcount']); // G2 students didn't submit yet.$this->assertEquals(0, $result['gradingsummary']['submissionsneedgradingcount']); // G2 students didn't submit yet.// Should not return information for all users (missing access to all groups capability for non-editing teacher).$result = mod_assign_external::get_submission_status($assign->get_instance()->id);$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertCount(0, $result['warnings']);$this->assertFalse(isset($result['gradingsummary']));// Should return all participants if we grant accessallgroups capability to the normal teacher role.$context = \context_course::instance($assign->get_instance()->course);$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id, true);accesslib_clear_all_caches_for_unit_testing();$result = mod_assign_external::get_submission_status($assign->get_instance()->id);$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertCount(0, $result['warnings']);$this->assertEquals(2, $result['gradingsummary']['participantcount']);$this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);$this->assertEquals(1, $result['gradingsummary']['submissionssubmittedcount']); // One student from G1 submitted.$this->assertEquals(1, $result['gradingsummary']['submissionsneedgradingcount']); // One student from G1 submitted.// Now check draft submissions.list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(false);$this->setUser($teacher);$result = mod_assign_external::get_submission_status($assign->get_instance()->id, 0, $g1->id);$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertCount(0, $result['warnings']);$this->assertEquals(1, $result['gradingsummary']['participantcount']);$this->assertEquals(1, $result['gradingsummary']['submissiondraftscount']); // We have a draft submission.$this->assertEquals(0, $result['gradingsummary']['submissionssubmittedcount']); // We have only draft submissions.$this->assertEquals(0, $result['gradingsummary']['submissionsneedgradingcount']); // We have only draft submissions.// Test assignment data.$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);}/*** Test get_submission_status for a reopened submission.*/public function test_get_submission_status_in_reopened_status(): void {global $USER;$this->resetAfterTest(true);list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);$studentsubmission = $assign->get_user_submission($student1->id, true);$this->setUser($teacher);// Grade and reopen.$feedbackpluginparams = array();$feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();$feedbackeditorparams = array('text' => 'Yeeha!','format' => 1);$feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;$result = mod_assign_external::save_grade($instance->id,$student1->id,50.0,-1,false,'released',false,$feedbackpluginparams);$USER->ignoresesskey = true;$assign->testable_process_add_attempt($student1->id);$this->setUser($student1);$result = mod_assign_external::get_submission_status($assign->get_instance()->id);// We expect debugging because of the $PAGE object, this won't happen in a normal WS request.$this->assertDebuggingCalled();$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertCount(0, $result['warnings']);$this->assertFalse(isset($result['gradingsummary']));$this->assertTrue($result['lastattempt']['submissionsenabled']);$this->assertTrue($result['lastattempt']['canedit']);$this->assertFalse($result['lastattempt']['cansubmit']);$this->assertFalse($result['lastattempt']['locked']);$this->assertFalse($result['lastattempt']['graded']);$this->assertEmpty($result['lastattempt']['extensionduedate']);$this->assertFalse($result['lastattempt']['blindmarking']);$this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);$this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);// Check new attempt reopened.$this->assertEquals($student1->id, $result['lastattempt']['submission']['userid']);$this->assertEquals(1, $result['lastattempt']['submission']['attemptnumber']);$this->assertEquals('reopened', $result['lastattempt']['submission']['status']);$this->assertEquals(0, $result['lastattempt']['submission']['groupid']);$this->assertEquals($assign->get_instance()->id, $result['lastattempt']['submission']['assignment']);$this->assertEquals(1, $result['lastattempt']['submission']['latest']);$this->assertCount(3, $result['lastattempt']['submission']['plugins']);// Now see feedback and the attempts history (remember, is a submission reopened).// Only 2 fields (no grade, no plugins data).$this->assertCount(2, $result['feedback']);// One previous attempt.$this->assertCount(1, $result['previousattempts']);$this->assertEquals(0, $result['previousattempts'][0]['attemptnumber']);$this->assertEquals(50, $result['previousattempts'][0]['grade']['grade']);$this->assertEquals($teacher->id, $result['previousattempts'][0]['grade']['grader']);$this->assertEquals($student1->id, $result['previousattempts'][0]['grade']['userid']);// Map plugins based on their type - we can't rely on them being in a// particular order, especially if 3rd party plugins are installed.$feedbackplugins = array();foreach ($result['previousattempts'][0]['feedbackplugins'] as $plugin) {$feedbackplugins[$plugin['type']] = $plugin;}$this->assertEquals('Yeeha!', $feedbackplugins['comments']['editorfields'][0]['text']);$submissionplugins = array();foreach ($result['previousattempts'][0]['submission']['plugins'] as $plugin) {$submissionplugins[$plugin['type']] = $plugin;}// Format expected online text.$onlinetext = 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>';list($expectedtext, $expectedformat) = \core_external\util::format_text($onlinetext,FORMAT_HTML,$assign->get_context(),'assignsubmission_onlinetext',ASSIGNSUBMISSION_ONLINETEXT_FILEAREA,$studentsubmission->id);$this->assertEquals($expectedtext, $submissionplugins['onlinetext']['editorfields'][0]['text']);$this->assertEquals($expectedformat, $submissionplugins['onlinetext']['editorfields'][0]['format']);$this->assertEquals('/', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);$this->assertEquals('t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filename']);// Test assignment data.$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);}/*** Test access control for get_submission_status.*/public function test_get_submission_status_access_control(): void {$this->resetAfterTest(true);list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();$this->setUser($student2);// Access control test.$this->expectException(\required_capability_exception::class);mod_assign_external::get_submission_status($assign->get_instance()->id, $student1->id);}/*** Test hidden grader for get_submission_status.*/public function test_get_submission_status_hidden_grader(): void {$this->resetAfterTest(true);list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);// Grade the assign for the student1.$this->setUser($teacher);$data = new \stdClass();$data->grade = '50.0';$data->assignfeedbackcomments_editor = ['text' => ''];$assign->testable_apply_grade_to_user($data, $student1->id, 0);$this->setUser($student1);// Check that the student can see the grader by default.$result = mod_assign_external::get_submission_status($assign->get_instance()->id);// We expect debugging because of the $PAGE object, this won't happen in a normal WS request.$this->assertDebuggingCalled();$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertTrue(isset($result['feedback']));$this->assertTrue(isset($result['feedback']['grade']));$this->assertEquals($teacher->id, $result['feedback']['grade']['grader']);// Now change the setting so the grader is hidden.$this->setAdminUser();$instance = $assign->get_instance();$instance->instance = $instance->id;$instance->hidegrader = true;$assign->update_instance($instance);$this->setUser($student1);// Check that the student cannot see the grader anymore.$result = mod_assign_external::get_submission_status($assign->get_instance()->id);$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertTrue(isset($result['feedback']));$this->assertTrue(isset($result['feedback']['grade']));$this->assertEquals(-1, $result['feedback']['grade']['grader']);// Check that the teacher can see the grader.$this->setUser($teacher);$result = mod_assign_external::get_submission_status($assign->get_instance()->id, $student1->id);$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertTrue(isset($result['feedback']));$this->assertTrue(isset($result['feedback']['grade']));$this->assertEquals($teacher->id, $result['feedback']['grade']['grader']);// Test assignment data.$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);}/*** Test get_submission_status with override for student.*/public function test_get_submission_status_with_override(): void {global $DB;$this->resetAfterTest(true);list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();$overridedata = new \stdClass();$overridedata->assignid = $assign->get_instance()->id;$overridedata->userid = $student1->id;$overridedata->allowsubmissionsfromdate = time() + YEARSECS;$DB->insert_record('assign_overrides', $overridedata);$result = mod_assign_external::get_submission_status($assign->get_instance()->id);// We expect debugging because of the $PAGE object, this won't happen in a normal WS request.$this->assertDebuggingCalled();$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertCount(0, $result['warnings']);$this->assertFalse(isset($result['gradingsummary']));$this->assertFalse(isset($result['feedback']));$this->assertFalse(isset($result['previousattempts']));$this->assertTrue($result['lastattempt']['submissionsenabled']);$this->assertFalse($result['lastattempt']['canedit']); // False because of override.$this->assertFalse($result['lastattempt']['cansubmit']);$this->assertFalse($result['lastattempt']['locked']);$this->assertFalse($result['lastattempt']['graded']);$this->assertEmpty($result['lastattempt']['extensionduedate']);$this->assertFalse($result['lastattempt']['blindmarking']);$this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);$this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);// Same assignment but user without override.$this->setUser($student2);$result = mod_assign_external::get_submission_status($assign->get_instance()->id);$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);// The submission is now in draft mode.$this->assertCount(0, $result['warnings']);$this->assertFalse(isset($result['gradingsummary']));$this->assertFalse(isset($result['feedback']));$this->assertFalse(isset($result['previousattempts']));$this->assertTrue($result['lastattempt']['submissionsenabled']);$this->assertTrue($result['lastattempt']['canedit']); // True because there is not override for this user.$this->assertFalse($result['lastattempt']['cansubmit']);$this->assertFalse($result['lastattempt']['locked']);$this->assertFalse($result['lastattempt']['graded']);$this->assertEmpty($result['lastattempt']['extensionduedate']);$this->assertFalse($result['lastattempt']['blindmarking']);$this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);$this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);// Test assignment data.$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);}/*** Test get_submission_status with time limit for student.** @covers \mod_assign_external::get_submission_status*/public function test_get_submission_status_with_time_limit_enabled(): void {$this->resetAfterTest();set_config('enabletimelimit', '1', 'assign');// Add time limit of 5 minutes to assignment. To edit activity, activity editor must not be empty.list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true, ['timelimit' => 300,'activityeditor' => ['text' => 'Test activity','format' => 1,],]);// Add an intro attachment.$fs = get_file_storage();$context = \context_module::instance($instance->cmid);$filerecord = array('contextid' => $context->id,'component' => 'mod_assign','filearea' => ASSIGN_INTROATTACHMENT_FILEAREA,'filename' => 'Test intro file','itemid' => 0,'filepath' => '/');$fs->create_file_from_string($filerecord, 'Test assign file');// Set optional param to indicate start time required.$_GET['action'] = 'editsubmission';$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);(new \assign($context, $cm, $cm->course))->get_user_submission(0, true);$result = mod_assign_external::get_submission_status($assign->get_instance()->id);// We expect debugging because of the $PAGE object, this won't happen in a normal WS request.$this->assertDebuggingCalled();$result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);$this->assertCount(0, $result['warnings']);$this->assertFalse(isset($result['gradingsummary']));$this->assertFalse(isset($result['feedback']));$this->assertFalse(isset($result['previousattempts']));$this->assertTrue($result['lastattempt']['submissionsenabled']);$this->assertFalse($result['lastattempt']['canedit']);$this->assertFalse($result['lastattempt']['cansubmit']);$this->assertFalse($result['lastattempt']['locked']);$this->assertFalse($result['lastattempt']['graded']);$this->assertEmpty($result['lastattempt']['extensionduedate']);$this->assertFalse($result['lastattempt']['blindmarking']);$this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);$this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);$this->assertEquals(300, $result['lastattempt']['timelimit']);$this->assertNotNull($result['lastattempt']['submission']['timestarted']);$this->assertLessThanOrEqual(time(), $result['lastattempt']['submission']['timestarted']);// Test assignment data.$this->assertNotEmpty($result['assignmentdata']);$this->assertEquals('Test activity', $result['assignmentdata']['activity']);$this->assertEquals(1, $result['assignmentdata']['activityformat']);$this->assertCount(2, $result['assignmentdata']['attachments']);$introattachments = $result['assignmentdata']['attachments']['intro'];$activityattachments = $result['assignmentdata']['attachments']['activity'];$this->assertCount(1, $introattachments);$intro = reset($introattachments);$this->assertEquals('Test intro file', $intro['filename']);$this->assertEmpty($activityattachments);}/*** get_participant should throw an excaption if the requested assignment doesn't exist.*/public function test_get_participant_no_assignment(): void {$this->resetAfterTest(true);$this->expectException(\moodle_exception::class);mod_assign_external::get_participant('-1', '-1', false);}/*** get_participant should throw a require_login_exception if the user doesn't have access* to view assignments.*/public function test_get_participant_no_view_capability(): void {global $DB;$this->resetAfterTest(true);$result = $this->create_assign_with_student_and_teacher();$assign = $result['assign'];$student = $result['student'];$course = $result['course'];$context = \context_course::instance($course->id);$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->setUser($student);assign_capability('mod/assign:view', CAP_PROHIBIT, $studentrole->id, $context->id, true);$this->expectException(\require_login_exception::class);mod_assign_external::get_participant($assign->id, $student->id, false);}/*** get_participant should throw a required_capability_exception if the user doesn't have access* to view assignment grades.*/public function test_get_participant_no_grade_capability(): void {global $DB;$this->resetAfterTest(true);$result = $this->create_assign_with_student_and_teacher();$assign = $result['assign'];$student = $result['student'];$teacher = $result['teacher'];$course = $result['course'];$context = \context_course::instance($course->id);$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->setUser($teacher);assign_capability('mod/assign:viewgrades', CAP_PROHIBIT, $teacherrole->id, $context->id, true);assign_capability('mod/assign:grade', CAP_PROHIBIT, $teacherrole->id, $context->id, true);accesslib_clear_all_caches_for_unit_testing();$this->expectException(\required_capability_exception::class);mod_assign_external::get_participant($assign->id, $student->id, false);}/*** get_participant should throw an exception if the user isn't enrolled in the course.*/public function test_get_participant_no_participant(): void {global $DB;$this->resetAfterTest(true);$result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true));$student = $this->getDataGenerator()->create_user();$assign = $result['assign'];$teacher = $result['teacher'];$this->setUser($teacher);$this->expectException(\moodle_exception::class);$result = mod_assign_external::get_participant($assign->id, $student->id, false);$result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);}/*** get_participant should return a summarised list of details with a different fullname if blind* marking is on for the requested assignment.*/public function test_get_participant_blind_marking(): void {global $DB;$this->resetAfterTest(true);$result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true));$assign = $result['assign'];$student = $result['student'];$teacher = $result['teacher'];$course = $result['course'];$context = \context_course::instance($course->id);$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->setUser($teacher);$result = mod_assign_external::get_participant($assign->id, $student->id, true);$result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);$this->assertEquals($student->id, $result['id']);$this->assertFalse(fullname($student) == $result['fullname']);$this->assertFalse($result['submitted']);$this->assertFalse($result['requiregrading']);$this->assertFalse($result['grantedextension']);$this->assertEquals('', $result['submissionstatus']);$this->assertTrue($result['blindmarking']);// Make sure we don't get any additional info.$this->assertArrayNotHasKey('user', $result);}/*** get_participant should return a summarised list of details if requested.*/public function test_get_participant_no_user(): void {global $DB;$this->resetAfterTest(true);$result = $this->create_assign_with_student_and_teacher();$assignmodule = $result['assign'];$student = $result['student'];$teacher = $result['teacher'];$course = $result['course'];$context = \context_course::instance($course->id);$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));// Create an assign instance to save a submission.set_config('submissionreceipts', 0, 'assign');$cm = get_coursemodule_from_instance('assign', $assignmodule->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$this->setUser($student);// Simulate a submission.$data = new \stdClass();$data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),'text' => 'Student submission text','format' => FORMAT_MOODLE);$notices = array();$assign->save_submission($data, $notices);$data = new \stdClass;$data->userid = $student->id;$assign->submit_for_grading($data, array());$this->setUser($teacher);$result = mod_assign_external::get_participant($assignmodule->id, $student->id, false);$result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);$this->assertEquals($student->id, $result['id']);$this->assertEquals(fullname($student), $result['fullname']);$this->assertTrue($result['submitted']);$this->assertTrue($result['requiregrading']);$this->assertFalse($result['grantedextension']);$this->assertEquals(ASSIGN_SUBMISSION_STATUS_SUBMITTED, $result['submissionstatus']);$this->assertFalse($result['blindmarking']);// Make sure we don't get any additional info.$this->assertArrayNotHasKey('user', $result);}/*** get_participant should return user details if requested.*/public function test_get_participant_full_details(): void {global $DB;$this->resetAfterTest(true);$result = $this->create_assign_with_student_and_teacher();$assign = $result['assign'];$student = $result['student'];$teacher = $result['teacher'];$course = $result['course'];$context = \context_course::instance($course->id);$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$this->setUser($teacher);$result = mod_assign_external::get_participant($assign->id, $student->id, true);$result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);// Check some of the extended properties we get when requesting the user.$this->assertEquals($student->id, $result['id']);// We should get user infomation back.$user = $result['user'];$this->assertFalse(empty($user));$this->assertEquals($student->firstname, $user['firstname']);$this->assertEquals($student->lastname, $user['lastname']);$this->assertEquals($student->email, $user['email']);}/*** get_participant should return group details if a group submission was* submitted.*/public function test_get_participant_group_submission(): void {global $DB;$this->resetAfterTest(true);$result = $this->create_assign_with_student_and_teacher(array('assignsubmission_onlinetext_enabled' => 1,'teamsubmission' => 1));$assignmodule = $result['assign'];$student = $result['student'];$teacher = $result['teacher'];$course = $result['course'];$context = \context_course::instance($course->id);$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));$group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));$cm = get_coursemodule_from_instance('assign', $assignmodule->id);$context = \context_module::instance($cm->id);$assign = new mod_assign_testable_assign($context, $cm, $course);groups_add_member($group, $student);$this->setUser($student);$submission = $assign->get_group_submission($student->id, $group->id, true);$submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;$assign->testable_update_submission($submission, $student->id, true, false);$data = new \stdClass();$data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),'text' => 'Submission text','format' => FORMAT_MOODLE);$plugin = $assign->get_submission_plugin_by_type('onlinetext');$plugin->save($submission, $data);$this->setUser($teacher);$result = mod_assign_external::get_participant($assignmodule->id, $student->id, false);$result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);// Check some of the extended properties we get when not requesting a summary.$this->assertEquals($student->id, $result['id']);$this->assertEquals($group->id, $result['groupid']);$this->assertEquals($group->name, $result['groupname']);}/*** Test get_participant() when relative dates mode is enabled on the course.** @dataProvider get_participant_relative_dates_provider* @param array $courseconfig the config to use when creating the course.* @param array $assignconfig the config to use when creating the assignment.* @param array $enrolconfig the enrolement to create.* @param array $expectedproperties array of expected assign properties.*/public function test_get_participant_relative_dates(array $courseconfig, array $assignconfig, array $enrolconfig,array $expectedproperties): void {$this->resetAfterTest();set_config('enablecourserelativedates', true); // Enable relative dates at site level.$course = $this->getDataGenerator()->create_course($courseconfig);$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');$assignconfig['course'] = $course->id;$instance = $generator->create_instance($assignconfig);$cm = get_coursemodule_from_instance('assign', $instance->id);$context = \context_module::instance($cm->id);$assign = new \assign($context, $cm, $course);$user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig));$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher', null, 'manual', time() - 50 * DAYSECS);$this->setUser($teacher);$result = mod_assign_external::get_participant($assign->get_instance()->id, $user->id, false);$result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);foreach ($expectedproperties as $propertyname => $propertyval) {$this->assertEquals($propertyval, $result[$propertyname]);}}/*** The test_get_participant_relative_dates data provider.*/public function get_participant_relative_dates_provider() {$timenow = time();return ['Student whose enrolment starts after the course start date, relative dates mode enabled' => ['courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual','startdate' => $timenow - 8 * DAYSECS],'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS]],'Student whose enrolment starts before the course start date, relative dates mode enabled' => ['courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual','startdate' => $timenow - 12 * DAYSECS],'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]],];}/*** Test for mod_assign_external::list_participants().** @throws coding_exception*/public function test_list_participants_user_info_with_special_characters(): void {global $CFG, $DB;$this->resetAfterTest(true);$CFG->showuseridentity = 'idnumber,email,phone1,phone2,department,institution';$data = $this->create_assign_with_student_and_teacher();$assignment = $data['assign'];$teacher = $data['teacher'];// Set data for student info that contain special characters.$student = $data['student'];$student->idnumber = '<\'"1am@wesome&c00l"\'>';$student->phone1 = '+63 (999) 888-7777';$student->phone2 = '(011) [15]4-123-4567';$student->department = 'Arts & Sciences & \' " ¢ £ © € ¥ ® < >';$student->institution = 'University of Awesome People & \' " ¢ £ © € ¥ ® < >';// Assert that we have valid user data.$this->assertTrue(\core_user::validate($student));// Update the user record.$DB->update_record('user', $student);$this->setUser($teacher);$participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, true, true);$participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);$this->assertCount(1, $participants);// Asser that we have a valid response data.$response = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);$this->assertEquals($response, $participants);// Check participant data.$participant = $participants[0];$this->assertEquals($student->idnumber, $participant['idnumber']);$this->assertEquals($student->email, $participant['email']);$this->assertEquals($student->phone1, $participant['phone1']);$this->assertEquals($student->phone2, $participant['phone2']);$this->assertEquals($student->department, $participant['department']);$this->assertEquals($student->institution, $participant['institution']);$this->assertEquals('', $participant['submissionstatus']);$this->assertArrayHasKey('enrolledcourses', $participant);$participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, false, true);$participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);// Check that the list of courses the participant is enrolled is not returned.$participant = $participants[0];$this->assertArrayNotHasKey('enrolledcourses', $participant);}/*** Test for the type of the user-related properties in mod_assign_external::list_participants_returns().*/public function test_list_participants_returns_user_property_types(): void {// Get user properties.$userdesc = core_user_external::user_description();$this->assertTrue(isset($userdesc->keys));$userproperties = array_keys($userdesc->keys);// Get returns description for mod_assign_external::list_participants_returns().$listreturns = mod_assign_external::list_participants_returns();$this->assertTrue(isset($listreturns->content));$listreturnsdesc = $listreturns->content->keys;// Iterate over list returns description's keys.foreach ($listreturnsdesc as $key => $desc) {// Check if key exists in user properties and the description has a type attribute.if (in_array($key, $userproperties) && isset($desc->type)) {try {// The \core_user::get_property_type() method might throw a coding_exception since// core_user_external::user_description() might contain properties that are not yet included in// core_user's $propertiescache.$propertytype = \core_user::get_property_type($key);// Assert that user-related property types match those of the defined in core_user.$this->assertEquals($propertytype, $desc->type);} catch (\coding_exception $e) {// All good.}}}}/*** Test for WS returning group.* @covers ::get_participant* @covers ::list_participants*/public function test_participants_info_with_groups(): void {global $CFG;$this->resetAfterTest(true);$CFG->showuseridentity = 'idnumber,email,phone1,phone2,department,institution';// Set up filtering.filter_set_global_state('multilang', TEXTFILTER_ON);filter_set_applies_to_strings('multilang', true);external_settings::get_instance()->set_filter(true);$result = $this->create_assign_with_student_and_teacher(['assignsubmission_onlinetext_enabled' => 1,'teamsubmission' => 1]);$assignmodule = $result['assign'];$student = $result['student'];$teacher = $result['teacher'];$course = $result['course'];$context = \context_course::instance($course->id);$gname = '<span class="multilang" lang="en">G (en)</span><span class="multilang" lang="es">G (es)</span>';$group = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => $gname]);groups_add_member($group, $student);$cm = get_coursemodule_from_instance('assign', $assignmodule->id);new mod_assign_testable_assign($context, $cm, $course);// Prepare submission.$this->setUser($teacher);// Test mod_assign_external::list_participants.$participants = mod_assign_external::list_participants($assignmodule->id, $group->id, '', 0, 0, false, true, true);$participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);$this->assertEquals($group->id, $participants[0]['groupid']);$this->assertEquals(format_string($gname, true), $participants[0]['groupname']);// Test mod_assign_external::get_participant.$participant = mod_assign_external::get_participant($assignmodule->id, $student->id, true);$participant = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $participant);$this->assertEquals($group->id, $participant['groupid']);$this->assertEquals(format_string($gname, true), $participant['groupname']);}/*** Test test_view_assign*/public function test_view_assign(): void {global $CFG;$CFG->enablecompletion = 1;$this->resetAfterTest();$this->setAdminUser();// Setup test data.$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id),array('completion' => 2, 'completionview' => 1));$context = \context_module::instance($assign->cmid);$cm = get_coursemodule_from_instance('assign', $assign->id);$result = mod_assign_external::view_assign($assign->id);$result = external_api::clean_returnvalue(mod_assign_external::view_assign_returns(), $result);$this->assertTrue($result['status']);$this->assertEmpty($result['warnings']);// Check completion status.$completion = new \completion_info($course);$completiondata = $completion->get_data($cm);$this->assertEquals(1, $completiondata->completionstate);}}