Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 38 and 311] [Versions 39 and 311]

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