__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

www-data@216.73.216.10: ~ $
<?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
 */
final 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,
            'maxattempts' => ASSIGN_UNLIMITED_ATTEMPTS,
            '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 static function get_participant_relative_dates_provider(): array {
        $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);
    }
}

Filemanager

Name Type Size Permission Actions
backup Folder 0777
behat Folder 0777
classes Folder 0777
event Folder 0777
external Folder 0777
fixtures Folder 0777
generator Folder 0777
privacy Folder 0777
search Folder 0777
base_test.php File 9.22 KB 0777
custom_completion_test.php File 8.28 KB 0777
dates_test.php File 7.72 KB 0777
downloader_test.php File 19.47 KB 0777
externallib_advanced_testcase.php File 7.33 KB 0777
externallib_test.php File 133.96 KB 0777
feedback_test.php File 5.67 KB 0777
generator.php File 4.65 KB 0777
generator_test.php File 2.07 KB 0777
lib_test.php File 58 KB 0777
locallib_participants_test.php File 7.53 KB 0777
locallib_test.php File 206.24 KB 0777
markerallocation_test.php File 5.58 KB 0777
notification_helper_test.php File 28.93 KB 0777
portfolio_caller_test.php File 10.05 KB 0777
Filemanager