Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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