Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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