From 1b2f9dc60c8ead84dbee4a6dcc991e0649535bda Mon Sep 17 00:00:00 2001 From: Ryan Wyllie Date: Tue, 19 Apr 2016 08:06:29 +0000 Subject: [PATCH] MDL-53821 mod_assign: Hide full name when blind marking Hide the student's full name in the new grading intferface when blind marking is on. --- .../build/grading_navigation_user_info.min.js | 2 +- .../amd/src/grading_navigation_user_info.js | 55 ++-- mod/assign/db/services.php | 9 + mod/assign/externallib.php | 102 +++++++ mod/assign/gradingtable.php | 10 +- mod/assign/locallib.php | 60 +++- mod/assign/tests/externallib_test.php | 272 ++++++++++++++++++ mod/assign/tests/locallib_test.php | 89 ++++++ mod/assign/version.php | 2 +- 9 files changed, 575 insertions(+), 26 deletions(-) diff --git a/mod/assign/amd/build/grading_navigation_user_info.min.js b/mod/assign/amd/build/grading_navigation_user_info.min.js index 5b5804a90e9..6f235fd7444 100644 --- a/mod/assign/amd/build/grading_navigation_user_info.min.js +++ b/mod/assign/amd/build/grading_navigation_user_info.min.js @@ -1 +1 @@ -define(["jquery","core/notification","core/ajax","core/templates"],function(a,b,c,d){var e=function(b){this._regionSelector=b,this._region=a(b),this._userCache=[],a(document).on("user-changed",this._refreshUserInfo.bind(this))};return e.prototype._regionSelector=null,e.prototype._userCache=null,e.prototype._region=null,e.prototype._lastUserId=0,e.prototype._refreshUserInfo=function(e,f){var g=a.Deferred();this._lastUserId!=f&&(this._lastUserId=f,d.render("mod_assign/loading",{}).done(function(e,h){if(this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,e,h),this._region.fadeIn("fast")}.bind(this)),0>f)return void d.render("mod_assign/grading_navigation_no_users",{}).done(function(a,b){this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,a,b),this._region.fadeIn("fast")}.bind(this))}.bind(this)).fail(b.exception);if("undefined"!=typeof this._userCache[f])g.resolve(this._userCache[f]);else{var i=c.call([{methodname:"core_user_get_users_by_field",args:{field:"id",values:[f]}}]);i[0].done(function(b){b.length<1?g.reject("No users"):(a.each(b,function(a,b){this._userCache[b.id]=b}.bind(this)),g.resolve(this._userCache[f]))}.bind(this)).fail(b.exception)}g.done(function(c){var e=a("[data-showuseridentity]").data("showuseridentity").split(","),f=[];c.courseid=a('[data-region="grading-navigation-panel"]').attr("data-courseid"),a.each(e,function(a,b){"undefined"!=typeof c[b]&&""!==c[b]&&(c.hasidentity=!0,f.push(c[b]))}),c.identity=f.join(", "),d.render("mod_assign/grading_navigation_user_summary",c).done(function(a,b){this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,a,b),this._region.fadeIn("fast")}.bind(this))}.bind(this)).fail(b.exception)}.bind(this)).fail(function(){d.render("mod_assign/grading_navigation_no_users",{}).done(function(a,b){this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,a,b),this._region.fadeIn("fast")}.bind(this))}.bind(this)).fail(b.exception)})}.bind(this)).fail(b.exception))},e}); \ No newline at end of file +define(["jquery","core/notification","core/ajax","core/templates"],function(a,b,c,d){var e=function(b){this._regionSelector=b,this._region=a(b),this._userCache={},a(document).on("user-changed",this._refreshUserInfo.bind(this))};return e.prototype._regionSelector=null,e.prototype._userCache=null,e.prototype._region=null,e.prototype._lastUserId=0,e.prototype._getAssignmentId=function(){return this._region.attr("data-assignmentid")},e.prototype._refreshUserInfo=function(e,f){var g=a.Deferred();this._lastUserId!=f&&(this._lastUserId=f,d.render("mod_assign/loading",{}).done(function(e,h){if(this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,e,h),this._region.fadeIn("fast")}.bind(this)),0>f)return void d.render("mod_assign/grading_navigation_no_users",{}).done(function(a,b){this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,a,b),this._region.fadeIn("fast")}.bind(this))}.bind(this)).fail(b.exception);if("undefined"!=typeof this._userCache[f])g.resolve(this._userCache[f]);else{var i=this._getAssignmentId(),j=c.call([{methodname:"mod_assign_get_participant",args:{userid:f,assignid:i,embeduser:!0}}]);j[0].done(function(a){a.hasOwnProperty("id")?(this._userCache[f]=a,g.resolve(this._userCache[f])):g.reject("No users")}.bind(this)).fail(b.exception)}g.done(function(c){var e=a("[data-showuseridentity]").data("showuseridentity").split(","),f=[];c.courseid=a('[data-region="grading-navigation-panel"]').attr("data-courseid"),c.user&&(a.each(e,function(a,b){"undefined"!=typeof c.user[b]&&""!==c.user[b]&&(c.hasidentity=!0,f.push(c.user[b]))}),c.identity=f.join(", "),c.user.profileimageurl&&(c.profileimageurl=c.user.profileimageurl)),d.render("mod_assign/grading_navigation_user_summary",c).done(function(a,b){this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,a,b),this._region.fadeIn("fast")}.bind(this))}.bind(this)).fail(b.exception)}.bind(this)).fail(function(){d.render("mod_assign/grading_navigation_no_users",{}).done(function(a,b){this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,a,b),this._region.fadeIn("fast")}.bind(this))}.bind(this)).fail(b.exception)}.bind(this))}.bind(this)).fail(b.exception))},e}); \ No newline at end of file diff --git a/mod/assign/amd/src/grading_navigation_user_info.js b/mod/assign/amd/src/grading_navigation_user_info.js index 617af6859c6..a58257fc793 100644 --- a/mod/assign/amd/src/grading_navigation_user_info.js +++ b/mod/assign/amd/src/grading_navigation_user_info.js @@ -34,7 +34,7 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates'], function( var UserInfo = function(selector) { this._regionSelector = selector; this._region = $(selector); - this._userCache = []; + this._userCache = {}; $(document).on('user-changed', this._refreshUserInfo.bind(this)); }; @@ -51,6 +51,17 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates'], function( /** @type {Integer} Remember the last user id to prevent unnessecary reloads. */ UserInfo.prototype._lastUserId = 0; + /** + * Get the assignment id + * + * @private + * @method _getAssignmentId + * @return int assignment id + */ + UserInfo.prototype._getAssignmentId = function() { + return this._region.attr('data-assignmentid'); + }; + /** * Get the user context - re-render the template in the page. * @@ -92,18 +103,21 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates'], function( promise.resolve(this._userCache[userid]); } else { // Load context from ajax. + var assignmentId = this._getAssignmentId(); var requests = ajax.call([{ - methodname: 'core_user_get_users_by_field', - args: { field: 'id', values: [ userid ] } + methodname: 'mod_assign_get_participant', + args: { + userid: userid, + assignid: assignmentId, + embeduser: true + } }]); - requests[0].done(function(result) { - if (result.length < 1) { + requests[0].done(function(participant) { + if (!participant.hasOwnProperty('id')) { promise.reject('No users'); } else { - $.each(result, function(index, user) { - this._userCache[user.id] = user; - }.bind(this)); + this._userCache[userid] = participant; promise.resolve(this._userCache[userid]); } }.bind(this)).fail(notification.exception); @@ -114,14 +128,22 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates'], function( identity = []; // Render the template. context.courseid = $('[data-region="grading-navigation-panel"]').attr('data-courseid'); - // Build a string for the visible identity fields listed in showuseridentity config setting. - $.each(identityfields, function(i, k) { - if (typeof context[k] !== 'undefined' && context[k] !== '') { - context.hasidentity = true; - identity.push(context[k]); + + if (context.user) { + // Build a string for the visible identity fields listed in showuseridentity config setting. + $.each(identityfields, function(i, k) { + if (typeof context.user[k] !== 'undefined' && context.user[k] !== '') { + context.hasidentity = true; + identity.push(context.user[k]); + } + }); + context.identity = identity.join(', '); + + // Add profile image url to context. + if (context.user.profileimageurl) { + context.profileimageurl = context.user.profileimageurl; } - }); - context.identity = identity.join(', '); + } templates.render('mod_assign/grading_navigation_user_summary', context).done(function(html, js) { // Update the page. @@ -139,7 +161,8 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates'], function( this._region.fadeIn("fast"); }.bind(this)); }.bind(this)).fail(notification.exception); - }); + } + .bind(this)); }.bind(this)).fail(notification.exception); }; diff --git a/mod/assign/db/services.php b/mod/assign/db/services.php index 8d41225d2ad..79217a8d0f3 100644 --- a/mod/assign/db/services.php +++ b/mod/assign/db/services.php @@ -210,4 +210,13 @@ $functions = array( 'ajax' => true, 'capabilities' => 'mod/assign:grade' ), + 'mod_assign_get_participant' => array( + 'classname' => 'mod_assign_external', + 'methodname' => 'get_participant', + 'classpath' => 'mod/assign/externallib.php', + 'description' => 'Get a participant for an assignment, with some summary info about their submissions.', + 'type' => 'read', + 'ajax' => true, + 'capabilities' => 'mod/assign:view, mod/assign:viewgrades' + ), ); diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index 45231139ea5..fa0b1a5b224 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die; require_once("$CFG->libdir/externallib.php"); +require_once("$CFG->dirroot/user/externallib.php"); require_once("$CFG->dirroot/mod/assign/locallib.php"); /** @@ -2762,4 +2763,105 @@ class mod_assign_external extends external_api { )) ); } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 3.1 + */ + public static function get_participant_parameters() { + return new external_function_parameters( + array( + 'assignid' => new external_value(PARAM_INT, 'assign instance id'), + 'userid' => new external_value(PARAM_INT, 'user id'), + 'embeduser' => new external_value(PARAM_BOOL, 'user id', VALUE_DEFAULT, false), + ) + ); + } + + /** + * Get the user participating in the given assignment. An error with code 'usernotincourse' + * is thrown is the user isn't a participant of the given assignment. + * + * @param int $assignid the assign instance id + * @param int $userid the user id + * @param bool $embeduser return user details (only applicable if not blind marking) + * @return array of warnings and status result + * @since Moodle 3.1 + * @throws moodle_exception + */ + public static function get_participant($assignid, $userid, $embeduser) { + global $DB, $CFG; + require_once($CFG->dirroot . "/mod/assign/locallib.php"); + require_once($CFG->dirroot . "/user/lib.php"); + + $params = self::validate_parameters(self::get_participant_parameters(), array( + 'assignid' => $assignid, + 'userid' => $userid, + 'embeduser' => $embeduser + )); + + // Request and permission validation. + $assign = $DB->get_record('assign', array('id' => $params['assignid']), 'id', MUST_EXIST); + list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign'); + + $context = context_module::instance($cm->id); + self::validate_context($context); + + $assign = new assign($context, null, null); + $assign->require_view_grades(); + + $participant = $assign->get_participant($params['userid']); + if (!$participant) { + // No participant found so we can return early. + throw new moodle_exception('usernotincourse'); + } + + $return = array( + 'id' => $participant->id, + 'fullname' => $participant->fullname, + 'submitted' => $participant->submitted, + 'requiregrading' => $participant->requiregrading, + 'blindmarking' => $assign->is_blind_marking(), + ); + + if (!empty($participant->groupid)) { + $return['groupid'] = $participant->groupid; + } + if (!empty($participant->groupname)) { + $return['groupname'] = $participant->groupname; + } + + // Skip the expensive lookup of user detail if we're blind marking or the caller + // hasn't asked for user details to be embedded. + if (!$assign->is_blind_marking() && $embeduser) { + $return['user'] = user_get_user_details($participant, $course); + } + + return $return; + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 3.1 + */ + public static function get_participant_returns() { + $userdescription = core_user_external::user_description(); + $userdescription->default = []; + $userdescription->required = VALUE_OPTIONAL; + + return new external_single_structure(array( + 'id' => new external_value(PARAM_INT, 'ID of the user'), + 'fullname' => new external_value(PARAM_NOTAGS, 'The fullname of the user'), + 'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'), + 'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'), + 'blindmarking' => new external_value(PARAM_BOOL, 'is blind marking enabled for this assignment'), + 'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL), + 'groupname' => new external_value(PARAM_NOTAGS, 'for group assignments this is the group name', VALUE_OPTIONAL), + 'user' => $userdescription, + )); + } } diff --git a/mod/assign/gradingtable.php b/mod/assign/gradingtable.php index 2c5b5bd862c..3f64179194b 100644 --- a/mod/assign/gradingtable.php +++ b/mod/assign/gradingtable.php @@ -835,8 +835,14 @@ class assign_grading_table extends table_sql implements renderable { if (!$this->is_downloading() && $this->hasgrade) { $urlparams = array('id' => $this->assignment->get_course_module()->id, 'rownum' => 0, - 'action' => 'grader', - 'userid' => $row->userid); + 'action' => 'grader'); + + if ($this->assignment->is_blind_marking()) { + $urlparams['blindid'] = $this->assignment->get_uniqueid_for_user($row->userid); + } else { + $urlparams['userid'] = $row->userid; + } + $url = new moodle_url('/mod/assign/view.php', $urlparams); $link = '' . get_string('grade') . ''; $grade .= $link . $separator; diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index de28b244598..2896ad1472c 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -1446,18 +1446,20 @@ class assign { } /** - * Get the submission status/grading status for all submissions in this assignment. + * Get the submission status/grading status for all submissions in this assignment for the + * given paticipants. + * * These statuses match the available filters (requiregrading, submitted, notsubmitted). * If this is a group assignment, group info is also returned. * - * @param int $currentgroup - * @return array List of user records with extra fields 'submitted', 'notsubmitted', 'requiregrading', 'groupid', 'groupname' + * @param array $participants an associative array where the key is the participant id and + * the value is the participant record. + * @return array an associative array where the key is the participant id and the value is + * the participant record. */ - public function list_participants_with_filter_status_and_group($currentgroup) { + private function get_submission_info_for_participants($participants) { global $DB; - $participants = $this->list_participants($currentgroup, false); - if (empty($participants)) { return $participants; } @@ -1523,6 +1525,24 @@ class assign { return $participants; } + /** + * Get the submission status/grading status for all submissions in this assignment. + * These statuses match the available filters (requiregrading, submitted, notsubmitted). + * If this is a group assignment, group info is also returned. + * + * @param int $currentgroup + * @return array List of user records with extra fields 'submitted', 'notsubmitted', 'requiregrading', 'groupid', 'groupname' + */ + public function list_participants_with_filter_status_and_group($currentgroup) { + $participants = $this->list_participants($currentgroup, false); + + if (empty($participants)) { + return $participants; + } else { + return $this->get_submission_info_for_participants($participants); + } + } + /** * Load a list of users enrolled in the current course with the specified permission and group. * 0 for no group. @@ -1564,6 +1584,29 @@ class assign { return $this->participants[$key]; } + /** + * Load a user if they are enrolled in the current course. Populated with submission + * status for this assignment. + * + * @param int $userid + * @return null|stdClass user record + */ + public function get_participant($userid) { + global $DB; + + $participant = $DB->get_record('user', array('id' => $userid)); + if (!$participant) { + return null; + } + + if (!is_enrolled($this->context, $participant, 'mod/assign:submit', $this->show_only_active_users())) { + return null; + } + + $result = $this->get_submission_info_for_participants(array($participant->id => $participant)); + return $result[$participant->id]; + } + /** * Load a count of valid teams for this assignment. * @@ -3779,6 +3822,11 @@ class assign { $o .= $this->get_renderer()->header(); $userid = optional_param('userid', 0, PARAM_INT); + $blindid = optional_param('blindid', 0, PARAM_INT); + + if (!$userid && $blindid) { + $userid = $this->get_user_id_for_uniqueid($blindid); + } $currentgroup = groups_get_activity_group($this->get_course_module(), true); $framegrader = new grading_app($userid, $currentgroup, $this); diff --git a/mod/assign/tests/externallib_test.php b/mod/assign/tests/externallib_test.php index 47e19536173..822d36e83c5 100644 --- a/mod/assign/tests/externallib_test.php +++ b/mod/assign/tests/externallib_test.php @@ -2037,4 +2037,276 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { mod_assign_external::get_submission_status($assign->get_instance()->id, $student1->id); } + + /** + * get_participant should throw an excaption if the requested assignment doesn't exist. + */ + public function test_get_participant_no_assignment() { + $this->resetAfterTest(true); + $this->setExpectedException('moodle_exception'); + mod_assign_external::get_participant('-1', '-1', false); + } + + /** + * get_participant should throw a require_login_exception if the user doesn't have access + * to view assignments. + */ + public function test_get_participant_no_view_capability() { + global $DB; + $this->resetAfterTest(true); + + $result = $this->create_assign_with_student_and_teacher(); + $assign = $result['assign']; + $student = $result['student']; + $course = $result['course']; + $context = context_course::instance($course->id); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + + $this->setUser($student); + assign_capability('mod/assign:view', CAP_PROHIBIT, $studentrole->id, $context->id, true); + + $this->setExpectedException('require_login_exception'); + mod_assign_external::get_participant($assign->id, $student->id, false); + } + + /** + * get_participant should throw a required_capability_exception if the user doesn't have access + * to view assignment grades. + */ + public function test_get_participant_no_grade_capability() { + global $DB; + $this->resetAfterTest(true); + + $result = $this->create_assign_with_student_and_teacher(); + $assign = $result['assign']; + $student = $result['student']; + $teacher = $result['teacher']; + $course = $result['course']; + $context = context_course::instance($course->id); + $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); + + $this->setUser($teacher); + assign_capability('mod/assign:viewgrades', CAP_PROHIBIT, $teacherrole->id, $context->id, true); + assign_capability('mod/assign:grade', CAP_PROHIBIT, $teacherrole->id, $context->id, true); + accesslib_clear_all_caches_for_unit_testing(); + + $this->setExpectedException('required_capability_exception'); + mod_assign_external::get_participant($assign->id, $student->id, false); + } + + /** + * get_participant should throw an exception if the user isn't enrolled in the course. + */ + public function test_get_participant_no_participant() { + global $DB; + $this->resetAfterTest(true); + + $result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true)); + $student = $this->getDataGenerator()->create_user(); + $assign = $result['assign']; + $teacher = $result['teacher']; + + $this->setUser($teacher); + + $this->setExpectedException('moodle_exception'); + $result = mod_assign_external::get_participant($assign->id, $student->id, false); + } + + /** + * get_participant should return a summarised list of details with a different fullname if blind + * marking is on for the requested assignment. + */ + public function test_get_participant_blind_marking() { + global $DB; + $this->resetAfterTest(true); + + $result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true)); + $assign = $result['assign']; + $student = $result['student']; + $teacher = $result['teacher']; + $course = $result['course']; + $context = context_course::instance($course->id); + $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); + + $this->setUser($teacher); + + $result = mod_assign_external::get_participant($assign->id, $student->id, true); + $this->assertEquals($student->id, $result['id']); + $this->assertFalse(fullname($student) == $result['fullname']); + $this->assertFalse($result['submitted']); + $this->assertFalse($result['requiregrading']); + $this->assertTrue($result['blindmarking']); + // Make sure we don't get any additional info. + $this->assertTrue(empty($result['user'])); + } + + /** + * get_participant should return a summarised list of details if requested. + */ + public function test_get_participant_no_user() { + global $DB; + $this->resetAfterTest(true); + + $result = $this->create_assign_with_student_and_teacher(); + $assignmodule = $result['assign']; + $student = $result['student']; + $teacher = $result['teacher']; + $course = $result['course']; + $context = context_course::instance($course->id); + $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); + + // Create an assign instance to save a submission. + set_config('submissionreceipts', 0, 'assign'); + + $cm = get_coursemodule_from_instance('assign', $assignmodule->id); + $context = context_module::instance($cm->id); + + $assign = new assign($context, $cm, $course); + + $this->setUser($student); + + // Simulate a submission. + $data = new stdClass(); + $data->onlinetext_editor = array( + 'itemid' => file_get_unused_draft_itemid(), + 'text' => 'Student submission text', + 'format' => FORMAT_MOODLE + ); + + $notices = array(); + $assign->save_submission($data, $notices); + + $data = new stdClass; + $data->userid = $student->id; + $assign->submit_for_grading($data, array()); + + $this->setUser($teacher); + + $result = mod_assign_external::get_participant($assignmodule->id, $student->id, false); + $this->assertEquals($student->id, $result['id']); + $this->assertEquals(fullname($student), $result['fullname']); + $this->assertTrue($result['submitted']); + $this->assertTrue($result['requiregrading']); + $this->assertFalse($result['blindmarking']); + // Make sure we don't get any additional info. + $this->assertTrue(empty($result['user'])); + } + + /** + * get_participant should return user details if requested. + */ + public function test_get_participant_full_details() { + global $DB; + $this->resetAfterTest(true); + + $result = $this->create_assign_with_student_and_teacher(); + $assign = $result['assign']; + $student = $result['student']; + $teacher = $result['teacher']; + $course = $result['course']; + $context = context_course::instance($course->id); + $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); + + $this->setUser($teacher); + + $result = mod_assign_external::get_participant($assign->id, $student->id, true); + // Check some of the extended properties we get when requesting the user. + $this->assertEquals($student->id, $result['id']); + // We should get user infomation back. + $user = $result['user']; + $this->assertFalse(empty($user)); + $this->assertEquals($student->firstname, $user['firstname']); + $this->assertEquals($student->lastname, $user['lastname']); + $this->assertEquals($student->email, $user['email']); + } + + /** + * get_participant should return group details if a group submission was + * submitted. + */ + public function test_get_participant_group_submission() { + global $DB, $CFG; + require_once($CFG->dirroot . '/mod/assign/tests/base_test.php'); + + $this->resetAfterTest(true); + + $result = $this->create_assign_with_student_and_teacher(array( + 'assignsubmission_onlinetext_enabled' => 1, + 'teamsubmission' => 1 + )); + $assignmodule = $result['assign']; + $student = $result['student']; + $teacher = $result['teacher']; + $course = $result['course']; + $context = context_course::instance($course->id); + $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); + $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + $cm = get_coursemodule_from_instance('assign', $assignmodule->id); + $context = context_module::instance($cm->id); + $assign = new testable_assign($context, $cm, $course); + + groups_add_member($group, $student); + + $this->setUser($student); + $submission = $assign->get_group_submission($student->id, $group->id, true); + $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; + $assign->testable_update_submission($submission, $student->id, true, false); + $data = new stdClass(); + $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(), + 'text' => 'Submission text', + 'format' => FORMAT_MOODLE); + $plugin = $assign->get_submission_plugin_by_type('onlinetext'); + $plugin->save($submission, $data); + + $this->setUser($teacher); + + $result = mod_assign_external::get_participant($assignmodule->id, $student->id, false); + // Check some of the extended properties we get when not requesting a summary. + $this->assertEquals($student->id, $result['id']); + $this->assertEquals($group->id, $result['groupid']); + $this->assertEquals($group->name, $result['groupname']); + } + + /** + * Create a a course, assignment module instance, student and teacher and enrol them in + * the course. + * + * @param array $params parameters to be provided to the assignment module creation + * @return array containing the course, assignment module, student and teacher + */ + private function create_assign_with_student_and_teacher($params = array()) { + global $DB; + + $course = $this->getDataGenerator()->create_course(); + $params = array_merge(array( + 'course' => $course->id, + 'name' => 'assignment', + 'intro' => 'assignment intro text', + ), $params); + + // Create a course and assignment and users. + $assign = $this->getDataGenerator()->create_module('assign', $params); + + $cm = get_coursemodule_from_instance('assign', $assign->id); + $context = context_module::instance($cm->id); + + $student = $this->getDataGenerator()->create_user(); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); + $teacher = $this->getDataGenerator()->create_user(); + $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); + + assign_capability('mod/assign:view', CAP_ALLOW, $teacherrole->id, $context->id, true); + assign_capability('mod/assign:viewgrades', CAP_ALLOW, $teacherrole->id, $context->id, true); + assign_capability('mod/assign:grade', CAP_ALLOW, $teacherrole->id, $context->id, true); + accesslib_clear_all_caches_for_unit_testing(); + + return array( + 'course' => $course, + 'assign' => $assign, + 'student' => $student, + 'teacher' => $teacher + ); + } } diff --git a/mod/assign/tests/locallib_test.php b/mod/assign/tests/locallib_test.php index 49583e70eee..2062696d040 100644 --- a/mod/assign/tests/locallib_test.php +++ b/mod/assign/tests/locallib_test.php @@ -721,6 +721,95 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase { $this->assertEquals(2, count($assign->list_participants(null, true))); } + public function test_get_participant_user_not_exist() { + $assign = $this->create_instance(array('grade' => 100)); + $this->assertNull($assign->get_participant('-1')); + } + + public function test_get_participant_not_enrolled() { + $assign = $this->create_instance(array('grade' => 100)); + $user = $this->getDataGenerator()->create_user(); + $this->assertNull($assign->get_participant($user->id)); + } + + public function test_get_participant_no_submission() { + $assign = $this->create_instance(array('grade' => 100)); + $student = $this->students[0]; + $participant = $assign->get_participant($student->id); + + $this->assertEquals($student->id, $participant->id); + $this->assertFalse($participant->submitted); + $this->assertFalse($participant->requiregrading); + } + + public function test_get_participant_with_ungraded_submission() { + $assign = $this->create_instance(array('grade' => 100)); + $student = $this->students[0]; + $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); + + $this->setUser($student); + + // Simulate a submission. + $data = new stdClass(); + $data->onlinetext_editor = array( + 'itemid' => file_get_unused_draft_itemid(), + 'text' => 'Student submission text', + 'format' => FORMAT_MOODLE + ); + + $notices = array(); + $assign->save_submission($data, $notices); + + $data = new stdClass; + $data->userid = $student->id; + $assign->submit_for_grading($data, array()); + + $participant = $assign->get_participant($student->id); + + $this->assertEquals($student->id, $participant->id); + $this->assertTrue($participant->submitted); + $this->assertTrue($participant->requiregrading); + } + + public function test_get_participant_with_graded_submission() { + $assign = $this->create_instance(array('grade' => 100)); + $student = $this->students[0]; + $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); + + $this->setUser($student); + + // Simulate a submission. + $data = new stdClass(); + $data->onlinetext_editor = array( + 'itemid' => file_get_unused_draft_itemid(), + 'text' => 'Student submission text', + 'format' => FORMAT_MOODLE + ); + + $notices = array(); + $assign->save_submission($data, $notices); + + $data = new stdClass; + $data->userid = $student->id; + $assign->submit_for_grading($data, array()); + + // This is to make sure the grade happens after the submission because + // we have no control over the timemodified values. + sleep(1); + // Grade the submission. + $this->setUser($this->teachers[0]); + + $data = new stdClass(); + $data->grade = '50.0'; + $assign->testable_apply_grade_to_user($data, $student->id, 0); + + $participant = $assign->get_participant($student->id); + + $this->assertEquals($student->id, $participant->id); + $this->assertTrue($participant->submitted); + $this->assertFalse($participant->requiregrading); + } + public function test_count_teams() { $this->create_extra_users(); $this->setUser($this->editingteachers[0]); diff --git a/mod/assign/version.php b/mod/assign/version.php index d302de32b90..2ed65ac9149 100644 --- a/mod/assign/version.php +++ b/mod/assign/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'mod_assign'; // Full name of the plugin (used for diagnostics). -$plugin->version = 2016041300; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2016041301; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2015111000; // Requires this Moodle version. $plugin->cron = 60;