Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

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