Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

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