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