Merge branch 'MDL-86006-main' of https://github.com/davewoloszyn/moodle
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
define("mod_assign/batch_set_marking_workflow_state",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
|
||||
/**
|
||||
* Javascript controller for the "Actions" panel at the bottom of the page.
|
||||
*
|
||||
* @module mod_assign/batch_set_marking_workflow_state
|
||||
* @copyright 2025 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @author Conn Warwicker <conn.warwicker@catalyst-eu.net>
|
||||
*/
|
||||
const filterOptions=(context,args)=>{const options=args[context];document.getElementById("id_markingworkflowstate").children.forEach((item=>{options.includes(item.value)?item.removeAttribute("disabled"):item.setAttribute("disabled","")}))};_exports.init=args=>{const workflowcontext=document.getElementById("id_workflowcontext");filterOptions(workflowcontext.value,args),workflowcontext.addEventListener("change",(e=>{filterOptions(e.target.value,args)}))}}));
|
||||
|
||||
//# sourceMappingURL=batch_set_marking_workflow_state.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"batch_set_marking_workflow_state.min.js","sources":["../src/batch_set_marking_workflow_state.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Javascript controller for the \"Actions\" panel at the bottom of the page.\n *\n * @module mod_assign/batch_set_marking_workflow_state\n * @copyright 2025 onwards Catalyst IT {@link http://www.catalyst-eu.net/}\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @author Conn Warwicker <[email protected]>\n */\n\n/**\n * Filter which options are enabled in the select menu.\n *\n * @param {object} context\n * @param {array} args\n */\nconst filterOptions = (context, args) => {\n const options = args[context];\n const workflowstate = document.getElementById('id_markingworkflowstate');\n workflowstate.children.forEach((item) => {\n if (options.includes(item.value)) {\n item.removeAttribute('disabled');\n } else {\n item.setAttribute('disabled', '');\n }\n });\n};\n\n/**\n * Initialise scripts.\n *\n * @param {array} args\n */\nexport const init = (args) => {\n const workflowcontext = document.getElementById('id_workflowcontext');\n filterOptions(workflowcontext.value, args);\n workflowcontext.addEventListener('change', (e) => {\n filterOptions(e.target.value, args);\n });\n};"],"names":["filterOptions","context","args","options","document","getElementById","children","forEach","item","includes","value","removeAttribute","setAttribute","workflowcontext","addEventListener","e","target"],"mappings":";;;;;;;;;MA8BMA,cAAgB,CAACC,QAASC,cACtBC,QAAUD,KAAKD,SACCG,SAASC,eAAe,2BAChCC,SAASC,SAASC,OACxBL,QAAQM,SAASD,KAAKE,OACtBF,KAAKG,gBAAgB,YAErBH,KAAKI,aAAa,WAAY,sBAUrBV,aACXW,gBAAkBT,SAASC,eAAe,sBAChDL,cAAca,gBAAgBH,MAAOR,MACrCW,gBAAgBC,iBAAiB,UAAWC,IACxCf,cAAce,EAAEC,OAAON,MAAOR"}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -5,6 +5,6 @@
|
||||
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("mod_assign/participant_selector",["core/ajax","jquery","core/templates"],(function(ajax,$,templates){return{processResults:function(selector,data){return data},transport:function(selector,query,success,failure){var assignmentid=$(selector).attr("data-assignmentid"),groupid=$(selector).attr("data-groupid"),filters=$('[data-region="configure-filters"] input[type="checkbox"]'),filterstrings=[];filters.each((function(index,element){filterstrings[$(element).attr("name")]=$(element).prop("checked")})),ajax.call([{methodname:"mod_assign_list_participants",args:{assignid:assignmentid,groupid:groupid,filter:query,limit:30,includeenrolments:!1,tablesort:!0}}])[0].then((function(results){var promises=[],identityfields=$("[data-showuseridentity]").data("showuseridentity").split(",");return $.each(results,(function(index,user){var ctx=user,identity=[],show=!0;filterstrings.filter_submitted&&!user.submitted&&(show=!1),filterstrings.filter_notsubmitted&&user.submitted&&(show=!1),filterstrings.filter_requiregrading&&!user.requiregrading&&(show=!1),filterstrings.filter_grantedextension&&!user.grantedextension&&(show=!1),show&&($.each(identityfields,(function(i,k){void 0!==user[k]&&""!==user[k]&&(ctx.hasidentity=!0,identity.push(user[k]))})),ctx.identity=identity.join(", "),promises.push(templates.render("mod_assign/list_participant_user_summary",ctx).then((function(html){return{value:user.id,label:html}}))))})),$.when.apply($,promises)})).then((function(){var users=[];arguments[0]&&(users=Array.prototype.slice.call(arguments)),success(users)})).catch(failure)}}}));
|
||||
define("mod_assign/participant_selector",["core/ajax","core/templates"],(function(ajax,templates){return{processResults:function(selector,data){return data},transport:function(selector,query,success,failure){const element=document.querySelector(selector);var assignmentid=element.getAttribute("data-assignmentid"),groupid=element.getAttribute("data-groupid"),filters=document.querySelectorAll('[data-region="configure-filters"] input[type="checkbox"]'),filterstrings=[];filters.forEach((e=>{let filterelement=document.querySelector(e);filterstrings[filterelement.getAttribute("name")]=filterelement.checked}));var marking=element.getAttribute("data-ismarking");ajax.call([{methodname:"mod_assign_list_participants",args:{assignid:assignmentid,groupid:groupid,filter:query,limit:30,includeenrolments:!1,tablesort:!0,marking:marking}}])[0].then((function(results){var promises=[],identityfields=document.querySelector("[data-showuseridentity]").dataset.showuseridentity.split(",");return results.forEach((user=>{var ctx=user,identity=[],show=!0;filterstrings.filter_submitted&&!user.submitted&&(show=!1),filterstrings.filter_notsubmitted&&user.submitted&&(show=!1),filterstrings.filter_requiregrading&&!user.requiregrading&&(show=!1),filterstrings.filter_grantedextension&&!user.grantedextension&&(show=!1),show&&(identityfields.forEach((k=>{void 0!==user[k]&&""!==user[k]&&(ctx.hasidentity=!0,identity.push(user[k]))})),ctx.identity=identity.join(", "),promises.push(templates.render("mod_assign/list_participant_user_summary",ctx).then((function(html){return{value:user.id,label:html}}))))})),Promise.all(promises)})).then((function(users){success(users)})).catch(failure)}}}));
|
||||
|
||||
//# sourceMappingURL=participant_selector.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,54 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Javascript controller for the "Actions" panel at the bottom of the page.
|
||||
*
|
||||
* @module mod_assign/batch_set_marking_workflow_state
|
||||
* @copyright 2025 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @author Conn Warwicker <[email protected]>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Filter which options are enabled in the select menu.
|
||||
*
|
||||
* @param {object} context
|
||||
* @param {array} args
|
||||
*/
|
||||
const filterOptions = (context, args) => {
|
||||
const options = args[context];
|
||||
const workflowstate = document.getElementById('id_markingworkflowstate');
|
||||
workflowstate.children.forEach((item) => {
|
||||
if (options.includes(item.value)) {
|
||||
item.removeAttribute('disabled');
|
||||
} else {
|
||||
item.setAttribute('disabled', '');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise scripts.
|
||||
*
|
||||
* @param {array} args
|
||||
*/
|
||||
export const init = (args) => {
|
||||
const workflowcontext = document.getElementById('id_workflowcontext');
|
||||
filterOptions(workflowcontext.value, args);
|
||||
workflowcontext.addEventListener('change', (e) => {
|
||||
filterOptions(e.target.value, args);
|
||||
});
|
||||
};
|
||||
@@ -39,6 +39,7 @@ define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete',
|
||||
this._filteredUsers = [];
|
||||
this._lastXofYUpdate = 0;
|
||||
this._firstLoadUsers = true;
|
||||
this._isMarking = false;
|
||||
|
||||
let url = new URL(window.location);
|
||||
if (parseInt(url.searchParams.get('treset')) > 0) {
|
||||
@@ -133,10 +134,11 @@ define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete',
|
||||
return false;
|
||||
}
|
||||
this._lastFilters = filter;
|
||||
this._isMarking = select.attr('data-ismarking') ?? false;
|
||||
|
||||
ajax.call([{
|
||||
methodname: 'mod_assign_list_participants',
|
||||
args: {assignid: assignmentid, groupid: groupid, filter: '', onlyids: true, tablesort: true},
|
||||
args: {assignid: assignmentid, groupid: groupid, filter: '', onlyids: true, tablesort: true, marking: this._isMarking},
|
||||
done: this._usersLoaded.bind(this),
|
||||
fail: notification.exception
|
||||
}]);
|
||||
|
||||
@@ -154,10 +154,13 @@ define([
|
||||
var data = form.serialize();
|
||||
var assignmentid = this._region.attr('data-assignmentid');
|
||||
|
||||
var vars = window.location.search.substring(1).split('&');
|
||||
var marker = vars.includes('action=marker');
|
||||
|
||||
// Now we can continue...
|
||||
ajax.call([{
|
||||
methodname: 'mod_assign_submit_grading_form',
|
||||
args: {assignmentid: assignmentid, userid: this._lastUserId, jsonformdata: JSON.stringify(data)},
|
||||
args: {assignmentid: assignmentid, userid: this._lastUserId, marker: marker, jsonformdata: JSON.stringify(data)},
|
||||
done: this._handleFormSubmissionResponse.bind(this, data, nextUserId, nextUser),
|
||||
fail: notification.exception
|
||||
}]);
|
||||
@@ -336,8 +339,12 @@ define([
|
||||
this._niceReplaceNodeContents(this._region, html, js).done(function() {
|
||||
if (userid > 0) {
|
||||
this._region.show();
|
||||
var vars = window.location.search.substring(1).split('&');
|
||||
var marker = vars.includes('action=marker');
|
||||
// Reload the grading form "fragment" for this user.
|
||||
var params = {userid: userid, attemptnumber: attemptnumber, jsonformdata: JSON.stringify(submissiondata)};
|
||||
var params = {userid: userid, attemptnumber: attemptnumber,
|
||||
jsonformdata: JSON.stringify(submissiondata),
|
||||
marker: marker};
|
||||
fragment.loadFragment('mod_assign', 'gradingpanel', contextid, params).done(function(html, js) {
|
||||
|
||||
// Reset whole grading page when there is a failure in retrieving the html
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* @copyright 2015 Damyon Wiese <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['core/ajax', 'jquery', 'core/templates'], function(ajax, $, templates) {
|
||||
define(['core/ajax', 'core/templates'], function(ajax, templates) {
|
||||
|
||||
|
||||
return /** @alias module:mod_assign/participants_selector */ {
|
||||
@@ -48,15 +48,19 @@ define(['core/ajax', 'jquery', 'core/templates'], function(ajax, $, templates) {
|
||||
* @param {Function} failure Failure handler
|
||||
*/
|
||||
transport: function(selector, query, success, failure) {
|
||||
var assignmentid = $(selector).attr('data-assignmentid');
|
||||
var groupid = $(selector).attr('data-groupid');
|
||||
var filters = $('[data-region="configure-filters"] input[type="checkbox"]');
|
||||
const element = document.querySelector(selector);
|
||||
var assignmentid = element.getAttribute('data-assignmentid');
|
||||
var groupid = element.getAttribute('data-groupid');
|
||||
var filters = document.querySelectorAll('[data-region="configure-filters"] input[type="checkbox"]');
|
||||
var filterstrings = [];
|
||||
|
||||
filters.each(function(index, element) {
|
||||
filterstrings[$(element).attr('name')] = $(element).prop('checked');
|
||||
filters.forEach((e) => {
|
||||
let filterelement = document.querySelector(e);
|
||||
filterstrings[filterelement.getAttribute('name')] = filterelement.checked;
|
||||
});
|
||||
|
||||
var marking = element.getAttribute('data-ismarking');
|
||||
|
||||
ajax.call([{
|
||||
methodname: 'mod_assign_list_participants',
|
||||
args: {
|
||||
@@ -65,14 +69,15 @@ define(['core/ajax', 'jquery', 'core/templates'], function(ajax, $, templates) {
|
||||
filter: query,
|
||||
limit: 30,
|
||||
includeenrolments: false,
|
||||
tablesort: true
|
||||
tablesort: true,
|
||||
marking: marking,
|
||||
}
|
||||
}])[0].then(function(results) {
|
||||
var promises = [];
|
||||
var identityfields = $('[data-showuseridentity]').data('showuseridentity').split(',');
|
||||
var identityfields = document.querySelector('[data-showuseridentity]').dataset.showuseridentity.split(',');
|
||||
|
||||
// We got the results, now we loop over them and render each one from a template.
|
||||
$.each(results, function(index, user) {
|
||||
results.forEach((user) => {
|
||||
var ctx = user,
|
||||
identity = [],
|
||||
show = true;
|
||||
@@ -90,7 +95,7 @@ define(['core/ajax', 'jquery', 'core/templates'], function(ajax, $, templates) {
|
||||
show = false;
|
||||
}
|
||||
if (show) {
|
||||
$.each(identityfields, function(i, k) {
|
||||
identityfields.forEach((k) => {
|
||||
if (typeof user[k] !== 'undefined' && user[k] !== '') {
|
||||
ctx.hasidentity = true;
|
||||
identity.push(user[k]);
|
||||
@@ -102,17 +107,8 @@ define(['core/ajax', 'jquery', 'core/templates'], function(ajax, $, templates) {
|
||||
}));
|
||||
}
|
||||
});
|
||||
// Do the dance for $.when()
|
||||
return $.when.apply($, promises);
|
||||
}).then(function() {
|
||||
var users = [];
|
||||
|
||||
// Determine if we've been passed any arguments..
|
||||
if (arguments[0]) {
|
||||
// Undo the $.when() dance from arguments object into an array..
|
||||
users = Array.prototype.slice.call(arguments);
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}).then(function(users) {
|
||||
success(users);
|
||||
return;
|
||||
}).catch(failure);
|
||||
|
||||
@@ -91,6 +91,8 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
|
||||
'maxattempts',
|
||||
'markingworkflow',
|
||||
'markingallocation',
|
||||
'markercount',
|
||||
'multimarkmethod',
|
||||
'markinganonymous',
|
||||
'preventsubmissionnotingroup',
|
||||
'activity',
|
||||
@@ -101,14 +103,30 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
|
||||
|
||||
$userflags = new backup_nested_element('userflags');
|
||||
|
||||
$userflag = new backup_nested_element('userflag', array('id'),
|
||||
array('userid',
|
||||
'assignment',
|
||||
'mailed',
|
||||
'locked',
|
||||
'extensionduedate',
|
||||
'workflowstate',
|
||||
'allocatedmarker'));
|
||||
$userflag = new backup_nested_element(
|
||||
'userflag',
|
||||
['id'],
|
||||
[
|
||||
'userid',
|
||||
'assignment',
|
||||
'mailed',
|
||||
'locked',
|
||||
'extensionduedate',
|
||||
'workflowstate',
|
||||
]
|
||||
);
|
||||
|
||||
$allocatedmarkers = new backup_nested_element('allocatedmarkers');
|
||||
|
||||
$allocatedmarker = new backup_nested_element(
|
||||
'allocatedmarker',
|
||||
['id'],
|
||||
[
|
||||
'student',
|
||||
'assignment',
|
||||
'marker',
|
||||
]
|
||||
);
|
||||
|
||||
$submissions = new backup_nested_element('submissions');
|
||||
|
||||
@@ -133,6 +151,22 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
|
||||
'penalty',
|
||||
'attemptnumber'));
|
||||
|
||||
$marks = new backup_nested_element('marks');
|
||||
|
||||
$mark = new backup_nested_element(
|
||||
'mark',
|
||||
['id'],
|
||||
[
|
||||
'assignment',
|
||||
'gradeid',
|
||||
'timecreated',
|
||||
'timemodified',
|
||||
'marker',
|
||||
'mark',
|
||||
'workflowstate',
|
||||
]
|
||||
);
|
||||
|
||||
$pluginconfigs = new backup_nested_element('plugin_configs');
|
||||
|
||||
$pluginconfig = new backup_nested_element('plugin_config', array('id'),
|
||||
@@ -148,10 +182,14 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
|
||||
// Build the tree.
|
||||
$assign->add_child($userflags);
|
||||
$userflags->add_child($userflag);
|
||||
$assign->add_child($allocatedmarkers);
|
||||
$allocatedmarkers->add_child($allocatedmarker);
|
||||
$assign->add_child($submissions);
|
||||
$submissions->add_child($submission);
|
||||
$assign->add_child($grades);
|
||||
$grades->add_child($grade);
|
||||
$assign->add_child($marks);
|
||||
$marks->add_child($mark);
|
||||
$assign->add_child($pluginconfigs);
|
||||
$pluginconfigs->add_child($pluginconfig);
|
||||
$assign->add_child($overrides);
|
||||
@@ -172,6 +210,15 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
|
||||
if ($userinfo) {
|
||||
$userflag->set_source_table('assign_user_flags',
|
||||
array('assignment' => backup::VAR_PARENTID));
|
||||
$allocatedmarker->set_source_table(
|
||||
'assign_allocated_marker',
|
||||
['assignment' => backup::VAR_PARENTID]
|
||||
);
|
||||
|
||||
$mark->set_source_table(
|
||||
'assign_mark',
|
||||
['assignment' => backup::VAR_PARENTID]
|
||||
);
|
||||
|
||||
$submissionparams = array('assignment' => backup::VAR_PARENTID);
|
||||
if (!$groupinfo) {
|
||||
@@ -198,7 +245,9 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
|
||||
|
||||
// Define id annotations.
|
||||
$userflag->annotate_ids('user', 'userid');
|
||||
$userflag->annotate_ids('user', 'allocatedmarker');
|
||||
$allocatedmarker->annotate_ids('user', 'student');
|
||||
$allocatedmarker->annotate_ids('user', 'marker');
|
||||
$mark->annotate_ids('user', 'marker');
|
||||
$submission->annotate_ids('user', 'userid');
|
||||
$submission->annotate_ids('group', 'groupid');
|
||||
$grade->annotate_ids('user', 'userid');
|
||||
|
||||
@@ -71,6 +71,16 @@ class restore_assign_activity_structure_step extends restore_activity_structure_
|
||||
$userflag = new restore_path_element('assign_userflag',
|
||||
'/activity/assign/userflags/userflag');
|
||||
$paths[] = $userflag;
|
||||
$allocatedmarker = new restore_path_element(
|
||||
'assign_allocatedmarker',
|
||||
'/activity/assign/allocatedmarkers/allocatedmarker'
|
||||
);
|
||||
$paths[] = $allocatedmarker;
|
||||
$mark = new restore_path_element(
|
||||
'assign_mark',
|
||||
'/activity/assign/marks/mark'
|
||||
);
|
||||
$paths[] = $mark;
|
||||
}
|
||||
|
||||
$paths[] = new restore_path_element('assign_override', '/activity/assign/overrides/override');
|
||||
@@ -131,6 +141,9 @@ class restore_assign_activity_structure_step extends restore_activity_structure_
|
||||
if (!isset($data->markingallocation)) {
|
||||
$data->markingallocation = 0;
|
||||
}
|
||||
if (!isset($data->markercount)) {
|
||||
$data->markercount = 1;
|
||||
}
|
||||
if (!isset($data->markinganonymous)) {
|
||||
$data->markinganonymous = 0;
|
||||
}
|
||||
@@ -213,9 +226,6 @@ class restore_assign_activity_structure_step extends restore_activity_structure_
|
||||
$data->assignment = $this->get_new_parentid('assign');
|
||||
|
||||
$data->userid = $this->get_mappingid('user', $data->userid);
|
||||
if (!empty($data->allocatedmarker)) {
|
||||
$data->allocatedmarker = $this->get_mappingid('user', $data->allocatedmarker);
|
||||
}
|
||||
if (!empty($data->extensionduedate)) {
|
||||
$data->extensionduedate = $this->apply_date_offset($data->extensionduedate);
|
||||
} else {
|
||||
@@ -226,6 +236,40 @@ class restore_assign_activity_structure_step extends restore_activity_structure_
|
||||
$newitemid = $DB->insert_record('assign_user_flags', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an allocated_marker restore.
|
||||
*
|
||||
* @param object $data The data in object form.
|
||||
*/
|
||||
protected function process_assign_allocatedmarker($data): void {
|
||||
global $DB;
|
||||
|
||||
$data = (object)$data;
|
||||
|
||||
$data->assignment = $this->get_new_parentid('assign');
|
||||
$data->student = $this->get_mappingid('user', $data->student);
|
||||
$data->marker = $this->get_mappingid('user', $data->marker);
|
||||
|
||||
$DB->insert_record('assign_allocated_marker', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process mark restore.
|
||||
*
|
||||
* @param object $data The data in object form.
|
||||
*/
|
||||
protected function process_assign_mark($data): void {
|
||||
global $DB;
|
||||
|
||||
$data = (object)$data;
|
||||
|
||||
$data->assignment = $this->get_new_parentid('assign');
|
||||
$data->marker = $this->get_mappingid('user', $data->marker);
|
||||
$data->gradeid = $this->get_mappingid('grade', $data->gradeid);
|
||||
|
||||
$DB->insert_record('assign_mark', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a grade restore
|
||||
* @param object $data The data in object form
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/formslib.php');
|
||||
require_once($CFG->dirroot . '/mod/assign/feedback/file/locallib.php');
|
||||
/**
|
||||
* Set allocated marker form.
|
||||
*
|
||||
@@ -32,6 +36,7 @@
|
||||
class mod_assign_batch_set_allocatedmarker_form extends moodleform {
|
||||
/**
|
||||
* Define this form - called by the parent constructor
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
@@ -41,7 +46,23 @@ class mod_assign_batch_set_allocatedmarker_form extends moodleform {
|
||||
$mform->addElement('static', 'userslist', get_string('selectedusers', 'assign'), $params['usershtml']);
|
||||
|
||||
$options = $params['markers'];
|
||||
$mform->addElement('select', 'allocatedmarker', get_string('allocatedmarker', 'assign'), $options);
|
||||
|
||||
$markercount = (!empty($params['markercount'])) ? $params['markercount'] : 1;
|
||||
$markerids = array_keys($options);
|
||||
|
||||
// If we do not have enough markers to meet the requested number, throw an exception with a meaningful message.
|
||||
if (count($markerids) < $markercount) {
|
||||
throw new \core\exception\moodle_exception('invalidmarkerallocation:notenoughmarkers', 'assign', '', [
|
||||
'markers' => count($markerids),
|
||||
'requested' => $markercount,
|
||||
]);
|
||||
}
|
||||
|
||||
$options = ['' => get_string('choosemarker', 'assign')] + $options;
|
||||
|
||||
for ($i = 1; $i <= $markercount; $i++) {
|
||||
$mform->addElement('select', "allocatedmarker{$i}", get_string('allocatedmarker', 'assign') . ' ' . $i, $options);
|
||||
}
|
||||
|
||||
$mform->addElement('hidden', 'id');
|
||||
$mform->setType('id', PARAM_INT);
|
||||
|
||||
@@ -34,6 +34,7 @@ class mod_assign_batch_set_marking_workflow_state_form extends moodleform {
|
||||
* Define this form - called by the parent constructor
|
||||
*/
|
||||
public function definition() {
|
||||
global $PAGE;
|
||||
$mform = $this->_form;
|
||||
$params = $this->_customdata;
|
||||
$formheader = get_string('batchsetmarkingworkflowstateforusers', 'assign', $params['userscount']);
|
||||
@@ -41,8 +42,38 @@ class mod_assign_batch_set_marking_workflow_state_form extends moodleform {
|
||||
$mform->addElement('header', 'general', $formheader);
|
||||
$mform->addElement('static', 'userslist', get_string('selectedusers', 'assign'), $params['usershtml']);
|
||||
|
||||
$options = $params['markingworkflowstates'];
|
||||
$mform->addElement('select', 'markingworkflowstate', get_string('markingworkflowstate', 'assign'), $options);
|
||||
$states = $params['markingworkflowstates'];
|
||||
|
||||
// If the assignment is using multi marking, do we want to set this workflow as the overall workflow for the submissions?
|
||||
// Or for our allocated mark on them?
|
||||
if ($params['assignment'] && $params['assignment']->is_using_multiple_marking()) {
|
||||
$options = new core\output\choicelist();
|
||||
$options->add_option(
|
||||
'mark',
|
||||
get_string('markverb', 'assign'),
|
||||
[
|
||||
'description' => get_string('workflowcontext_help_mark', 'assign'),
|
||||
],
|
||||
);
|
||||
$options->add_option(
|
||||
'grade',
|
||||
get_string('gradenoun'),
|
||||
[
|
||||
'description' => get_string('workflowcontext_help_grade', 'assign'),
|
||||
],
|
||||
);
|
||||
$mform->addElement('choicedropdown', 'workflowcontext', get_string('workflowcontext', 'assign'), $options);
|
||||
$PAGE->requires->js_call_amd('mod_assign/batch_set_marking_workflow_state', 'init', [[
|
||||
'mark' => [
|
||||
ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
|
||||
ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
|
||||
ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
|
||||
],
|
||||
'grade' => array_keys($states),
|
||||
]]);
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'markingworkflowstate', get_string('markingworkflowstate', 'assign'), $states);
|
||||
|
||||
// Don't allow notification to be sent until in "Released" state.
|
||||
$mform->addElement('selectyesno', 'sendstudentnotifications', get_string('sendstudentnotifications', 'assign'));
|
||||
|
||||
@@ -30,15 +30,20 @@ use moodle_url;
|
||||
class actionmenu implements templatable, renderable {
|
||||
|
||||
/** @var int The course module ID. */
|
||||
private $cmid;
|
||||
private int $cmid;
|
||||
|
||||
/** @var bool Are we using marking workflow? */
|
||||
private bool $markingworkflow;
|
||||
|
||||
/**
|
||||
* Constructor for this object.
|
||||
*
|
||||
* @param int $cmid The course module ID.
|
||||
* @param bool $markingworkflow Are we using marking workflow?
|
||||
*/
|
||||
public function __construct(int $cmid) {
|
||||
public function __construct(int $cmid, bool $markingworkflow) {
|
||||
$this->cmid = $cmid;
|
||||
$this->markingworkflow = $markingworkflow;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,8 +56,14 @@ class actionmenu implements templatable, renderable {
|
||||
$return = [];
|
||||
|
||||
if (has_capability('mod/assign:grade', \context_module::instance($this->cmid))) {
|
||||
$gradelink = new moodle_url('/mod/assign/view.php', ['id' => $this->cmid, 'action' => 'grader']);
|
||||
$return['gradelink'] = $gradelink->out(false);
|
||||
// If we are using multiple markers, then we'll have marker columns which need links to the marker page.
|
||||
if ($this->markingworkflow) {
|
||||
$gradelink = new moodle_url('/mod/assign/view.php', ['id' => $this->cmid, 'action' => 'marker']);
|
||||
$return['marklink'] = $gradelink->out(false);
|
||||
} else {
|
||||
$gradelink = new moodle_url('/mod/assign/view.php', ['id' => $this->cmid, 'action' => 'grader']);
|
||||
$return['gradelink'] = $gradelink->out(false);
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
||||
@@ -61,18 +61,27 @@ class grading_app implements templatable, renderable {
|
||||
*/
|
||||
public $participants = [];
|
||||
|
||||
/**
|
||||
* @var bool - True to show fields for marking, false to show fields for
|
||||
* grading (used when there are multiple markers).
|
||||
*/
|
||||
public $marker = false;
|
||||
|
||||
/**
|
||||
* Constructor for this renderable.
|
||||
*
|
||||
* @param int $userid The user we will open the grading app too.
|
||||
* @param int $groupid If groups are enabled this is the current course group.
|
||||
* @param \assign $assignment The assignment class
|
||||
* @param bool $marker If assignment instance uses multiple markers $marker
|
||||
* is true to show fields for marking (as opposed to grading).
|
||||
*/
|
||||
public function __construct($userid, $groupid, $assignment) {
|
||||
public function __construct($userid, $groupid, $assignment, $marker = false) {
|
||||
$this->userid = $userid;
|
||||
$this->groupid = $groupid;
|
||||
$this->assignment = $assignment;
|
||||
$this->participants = $assignment->list_participants_with_filter_status_and_group($groupid);
|
||||
$this->marker = $marker;
|
||||
if (!$this->userid && count($this->participants)) {
|
||||
$this->userid = reset($this->participants)->id;
|
||||
}
|
||||
@@ -101,6 +110,7 @@ class grading_app implements templatable, renderable {
|
||||
$export->hasmarkingworkflow = count($export->markingworkflowfilters) > 0;
|
||||
$export->markingallocationfilters = $this->assignment->get_marking_allocation_filters(true);
|
||||
$export->hasmarkingallocation = count($export->markingallocationfilters) > 0;
|
||||
$export->ismarking = (int)$this->marker;
|
||||
|
||||
$num = 1;
|
||||
foreach ($this->participants as $idx => $record) {
|
||||
|
||||
@@ -108,11 +108,23 @@ class provider implements
|
||||
'assignment' => 'privacy:metadata:assignmentid',
|
||||
'userid' => 'privacy:metadata:userid'
|
||||
];
|
||||
$assignmark = [
|
||||
'assignment' => 'privacy:metadata:assignmentid',
|
||||
'gradeid' => 'privacy:metadata:grade',
|
||||
'marker' => 'privacy:metadata:userid',
|
||||
];
|
||||
$assignallocatedusers = [
|
||||
'assignment' => 'privacy:metadata:assignmentid',
|
||||
'studentid' => 'privacy:metadata:userid',
|
||||
'marker' => 'privacy:metadata:userid',
|
||||
];
|
||||
$collection->add_database_table('assign_grades', $assigngrades, 'privacy:metadata:assigngrades');
|
||||
$collection->add_database_table('assign_overrides', $assignoverrides, 'privacy:metadata:assignoverrides');
|
||||
$collection->add_database_table('assign_submission', $assignsubmission, 'privacy:metadata:assignsubmissiondetail');
|
||||
$collection->add_database_table('assign_user_flags', $assignuserflags, 'privacy:metadata:assignuserflags');
|
||||
$collection->add_database_table('assign_user_mapping', $assignusermapping, 'privacy:metadata:assignusermapping');
|
||||
$collection->add_database_table('assign_mark', $assignmark, 'privacy:metadata:assignmark');
|
||||
$collection->add_database_table('assign_allocated_marker', $assignallocatedusers, 'privacy:metadata:assignallocatedmarker');
|
||||
$collection->add_user_preference('assign_perpage', 'privacy:metadata:assignperpage');
|
||||
$collection->add_user_preference('assign_filter', 'privacy:metadata:assignfilter');
|
||||
$collection->add_user_preference('assign_markerfilter', 'privacy:metadata:assignmarkerfilter');
|
||||
@@ -194,6 +206,37 @@ class provider implements
|
||||
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
// Contexts where the given user has assignment marks.
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {course_modules} cm
|
||||
JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
|
||||
JOIN {assign} a ON cm.instance = a.id
|
||||
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
|
||||
JOIN {assign_grades} ag ON a.id = ag.assignment
|
||||
JOIN {assign_mark} am ON am.gradeid = ag.id
|
||||
WHERE ag.userid = :userid";
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
// Contexts where the given user has assigned assignment marks.
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {course_modules} cm
|
||||
JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
|
||||
JOIN {assign} a ON cm.instance = a.id
|
||||
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
|
||||
JOIN {assign_mark} am ON am.assignment = a.id
|
||||
WHERE am.marker = :userid";
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
// Contexts where the given user is either an allocated marker or has an allocated marker.
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {course_modules} cm
|
||||
JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
|
||||
JOIN {assign} a ON cm.instance = a.id
|
||||
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
|
||||
JOIN {assign_allocated_marker} am ON am.assignment = a.id
|
||||
WHERE am.marker = :graderid OR am.student = :userid";
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE,
|
||||
'get_context_for_userid_within_feedback', [$userid, $contextlist]);
|
||||
manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE,
|
||||
@@ -266,6 +309,29 @@ class provider implements
|
||||
WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
|
||||
$userlist->add_from_sql('userid', $sql, $params);
|
||||
|
||||
// Users who are either a student with a mark, or the teacher who has given a mark.
|
||||
$sql = "SELECT g.userid, am.marker
|
||||
FROM {context} ctx
|
||||
JOIN {course_modules} cm ON cm.id = ctx.instanceid
|
||||
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
|
||||
JOIN {assign} a ON a.id = cm.instance
|
||||
JOIN {assign_grades} g ON a.id = g.assignment
|
||||
JOIN {assign_mark} am ON am.gradeid = g.id
|
||||
WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
|
||||
$userlist->add_from_sql('userid', $sql, $params);
|
||||
$userlist->add_from_sql('marker', $sql, $params);
|
||||
|
||||
// Users who are either a student with an allocated marker, or the allocated marker.
|
||||
$sql = "SELECT am.student, am.marker
|
||||
FROM {context} ctx
|
||||
JOIN {course_modules} cm ON cm.id = ctx.instanceid
|
||||
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
|
||||
JOIN {assign} a ON a.id = cm.instance
|
||||
JOIN {assign_allocated_marker} am ON am.assignment = a.id
|
||||
WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
|
||||
$userlist->add_from_sql('student', $sql, $params);
|
||||
$userlist->add_from_sql('marker', $sql, $params);
|
||||
|
||||
manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_USER_INTERFACE,
|
||||
'get_userids_from_context', [$userlist]);
|
||||
manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_USER_INTERFACE,
|
||||
@@ -302,6 +368,8 @@ class provider implements
|
||||
|
||||
static::export_overrides($context, $assign, $user);
|
||||
static::export_submission($assign, $user, $context, []);
|
||||
static::export_marks($assign, $user, $context);
|
||||
static::export_allocations($assign, $user, $context);
|
||||
// Meta data.
|
||||
self::store_assign_user_flags($context, $assign, $user->id);
|
||||
if ($assign->is_blind_marking()) {
|
||||
@@ -349,6 +417,8 @@ class provider implements
|
||||
$DB->delete_records('assign_submission', ['assignment' => $assign->get_instance()->id]);
|
||||
$DB->delete_records('assign_user_flags', ['assignment' => $assign->get_instance()->id]);
|
||||
$DB->delete_records('assign_user_mapping', ['assignment' => $assign->get_instance()->id]);
|
||||
static::delete_allocated_markers_for_users($assign);
|
||||
static::delete_marks_for_users($assign);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,6 +464,8 @@ class provider implements
|
||||
static::delete_overrides_for_users($assign, [$user->id]);
|
||||
$DB->delete_records('assign_user_flags', ['assignment' => $assignid, 'userid' => $user->id]);
|
||||
$DB->delete_records('assign_user_mapping', ['assignment' => $assignid, 'userid' => $user->id]);
|
||||
static::delete_allocated_markers_for_users($assign, [$user->id]);
|
||||
static::delete_marks_for_users($assign, [$user->id]);
|
||||
$DB->delete_records('assign_grades', ['assignment' => $assignid, 'userid' => $user->id]);
|
||||
$DB->delete_records('assign_submission', ['assignment' => $assignid, 'userid' => $user->id]);
|
||||
}
|
||||
@@ -440,6 +512,8 @@ class provider implements
|
||||
$params['assignment'] = $assignid;
|
||||
$DB->delete_records_select('assign_user_flags', "assignment = :assignment AND userid $sql", $params);
|
||||
$DB->delete_records_select('assign_user_mapping', "assignment = :assignment AND userid $sql", $params);
|
||||
static::delete_allocated_markers_for_users($assign, $userids);
|
||||
static::delete_marks_for_users($assign, $userids);
|
||||
$DB->delete_records_select('assign_grades', "assignment = :assignment AND userid $sql", $params);
|
||||
$DB->delete_records_select('assign_submission', "assignment = :assignment AND userid $sql", $params);
|
||||
}
|
||||
@@ -674,4 +748,127 @@ class provider implements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all allocated markers for an array of users, for the given assignment.
|
||||
*
|
||||
* @param \assign $assign The assignment object.
|
||||
* @param array $userids If empty, delete all marks for the assignment.
|
||||
*/
|
||||
protected static function delete_allocated_markers_for_users(\assign $assign, array $userids = []): void {
|
||||
global $DB;
|
||||
|
||||
if ($userids) {
|
||||
[$insql, $inparams] = $DB->get_in_or_equal($userids);
|
||||
$params = [$assign->get_instance()->id];
|
||||
// We need this twice as we're using the same $insql for student and marker.
|
||||
$params = array_merge($params, $inparams);
|
||||
$params = array_merge($params, $inparams);
|
||||
$sql = "
|
||||
SELECT id
|
||||
FROM {assign_allocated_marker}
|
||||
WHERE assignment = ?
|
||||
AND (student {$insql} OR marker {$insql})
|
||||
";
|
||||
$DB->delete_records_subquery('assign_allocated_marker', 'id', 'id', $sql, $params);
|
||||
} else {
|
||||
$DB->delete_records('assign_allocated_marker', ['assignment' => $assign->get_instance()->id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all assignment marks for an array of users, for the given assignment.
|
||||
*
|
||||
* @param \assign $assign The assignment object.
|
||||
* @param array $userids If empty, delete all marks for the assignment.
|
||||
*/
|
||||
protected static function delete_marks_for_users(\assign $assign, array $userids = []): void {
|
||||
global $DB;
|
||||
if ($userids) {
|
||||
[$insql, $inparams] = $DB->get_in_or_equal($userids);
|
||||
$params = [$assign->get_instance()->id];
|
||||
// We need this twice as we're using the same $insql for student and marker.
|
||||
$params = array_merge($params, $inparams);
|
||||
$params = array_merge($params, $inparams);
|
||||
$sql = "
|
||||
SELECT am.id
|
||||
FROM {assign_mark} am
|
||||
LEFT JOIN {assign_grades} ag ON ag.id = am.gradeid
|
||||
WHERE am.assignment = ?
|
||||
AND (ag.userid {$insql} OR am.marker {$insql})
|
||||
";
|
||||
$DB->delete_records_subquery('assign_mark', 'id', 'id', $sql, $params);
|
||||
} else {
|
||||
$DB->delete_records('assign_mark', ['assignment' => $assign->get_instance()->id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the user's marks for the given assignment.
|
||||
*
|
||||
* @param \assign $assign The assignment object
|
||||
* @param \stdClass $user The user object
|
||||
* @param \context $context The context
|
||||
*/
|
||||
protected static function export_marks(\assign $assign, \stdClass $user, \context $context): void {
|
||||
global $DB;
|
||||
|
||||
$records = $DB->get_records_sql(
|
||||
"
|
||||
SELECT am.*
|
||||
FROM {assign_mark} am
|
||||
LEFT JOIN {assign_grades} ag on ag.id = am.gradeid
|
||||
WHERE am.assignment = :assignment
|
||||
AND (ag.userid = :uid1 OR am.marker = :uid2)
|
||||
",
|
||||
[
|
||||
'assignment' => $assign->get_instance()->id,
|
||||
'uid1' => $user->id,
|
||||
'uid2' => $user->id,
|
||||
]
|
||||
);
|
||||
|
||||
$data = [];
|
||||
foreach ($records as $row) {
|
||||
$row = (array)$row;
|
||||
unset($row['id']);
|
||||
$data[] = $row;
|
||||
}
|
||||
|
||||
writer::with_context($context)->export_data([get_string('marks', 'mod_assign')], (object) ['data' => $data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the user's marker allocations for the given assignment.
|
||||
*
|
||||
* @param \assign $assign The assignment object
|
||||
* @param \stdClass $user The user object
|
||||
* @param \context $context The context
|
||||
*/
|
||||
protected static function export_allocations(\assign $assign, \stdClass $user, \context $context): void {
|
||||
global $DB;
|
||||
|
||||
$records = $DB->get_records_sql(
|
||||
"
|
||||
SELECT *
|
||||
FROM {assign_allocated_marker}
|
||||
WHERE assignment = :assignment
|
||||
AND (student = :uid1 OR marker = :uid2)
|
||||
",
|
||||
[
|
||||
'assignment' => $assign->get_instance()->id,
|
||||
'uid1' => $user->id,
|
||||
'uid2' => $user->id,
|
||||
]
|
||||
);
|
||||
|
||||
$data = [];
|
||||
foreach ($records as $row) {
|
||||
$row = (array)$row;
|
||||
unset($row['id']);
|
||||
$data[] = $row;
|
||||
}
|
||||
|
||||
writer::with_context($context)->export_data([get_string('markerallocations', 'mod_assign')], (object) ['data' => $data]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
<FIELD NAME="maxattempts" TYPE="int" LENGTH="6" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="What is the maximum number of student attempts allowed for this assignment? -1 means unlimited."/>
|
||||
<FIELD NAME="markingworkflow" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If enabled, marking workflow features will be used in this assignment."/>
|
||||
<FIELD NAME="markingallocation" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If enabled, marking allocation features will be used in this assignment"/>
|
||||
<FIELD NAME="markercount" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Number of markers for this assignment, for example, 2 for double marking"/>
|
||||
<FIELD NAME="multimarkmethod" TYPE="char" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="How to calculate grade when markercount greater than 1"/>
|
||||
<FIELD NAME="multimarkrounding" TYPE="int" LENGTH="2" NOTNULL="false" SEQUENCE="false" COMMENT="How to round the grade calculation if using an average of marks"/>
|
||||
<FIELD NAME="markinganonymous" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If enabled, marking anonymous features will be used in this assignment"/>
|
||||
<FIELD NAME="sendstudentnotifications" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Default for send student notifications checkbox when grading."/>
|
||||
<FIELD NAME="preventsubmissionnotingroup" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If enabled a user will be unable to make a submission unless they are a member of a group."/>
|
||||
@@ -138,7 +141,6 @@
|
||||
<FIELD NAME="mailed" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Has the student been sent a notification about this grade update?"/>
|
||||
<FIELD NAME="extensionduedate" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="An extension date assigned to an individual student."/>
|
||||
<FIELD NAME="workflowstate" TYPE="char" LENGTH="20" NOTNULL="false" SEQUENCE="false" COMMENT="The current workflow state of the grade"/>
|
||||
<FIELD NAME="allocatedmarker" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The allocated marker to this submission"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
@@ -168,5 +170,37 @@
|
||||
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="assign_mark" COMMENT="Marks from multiple markers for a single submission">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="assignment" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The ID of the assignment this mark applies to"/>
|
||||
<FIELD NAME="gradeid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The ID of an assign_grades row"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time this mark was created"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time this mark was modified"/>
|
||||
<FIELD NAME="marker" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID of the marker"/>
|
||||
<FIELD NAME="mark" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="Mark given by marker"/>
|
||||
<FIELD NAME="workflowstate" TYPE="char" LENGTH="20" NOTNULL="false" SEQUENCE="false" COMMENT="The current workflow state of the mark"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="assignment" TYPE="foreign" FIELDS="assignment" REFTABLE="assign" REFFIELDS="id" COMMENT="The assignment id these marks apply to"/>
|
||||
<KEY NAME="gradeid" TYPE="foreign" FIELDS="gradeid" REFTABLE="assign_grades" REFFIELDS="id" COMMENT="Final grade record that this mark relates to"/>
|
||||
<KEY NAME="marker" TYPE="foreign" FIELDS="marker" REFTABLE="user" REFFIELDS="id" COMMENT="Marker who gave this mark"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="assign_allocated_marker" COMMENT="One or more teachers allocated to mark individual submissions">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="student" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="User ID of student this marker is allocated to"/>
|
||||
<FIELD NAME="assignment" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The id of the assignment this marker is allocated to"/>
|
||||
<FIELD NAME="marker" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="User ID of teacher allocated to mark this student"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="student" TYPE="foreign" FIELDS="student" REFTABLE="user" REFFIELDS="id" COMMENT="The id field of the user table"/>
|
||||
<KEY NAME="assignment" TYPE="foreign" FIELDS="assignment" REFTABLE="assign" REFFIELDS="id" COMMENT="The assignment id these flags apply to"/>
|
||||
<KEY NAME="marker" TYPE="foreign" FIELDS="marker" REFTABLE="user" REFFIELDS="id" COMMENT="The id field of the user table"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
|
||||
@@ -131,5 +131,91 @@ function xmldb_assign_upgrade($oldversion) {
|
||||
upgrade_mod_savepoint(true, 2026022300, 'assign');
|
||||
}
|
||||
|
||||
if ($oldversion < 2026022301) {
|
||||
// Define field markercount to be added to assign.
|
||||
$table = new xmldb_table('assign');
|
||||
$field = new xmldb_field('markercount', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '1', 'markingallocation');
|
||||
// Conditionally launch add field markercount.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define field multimarkmethod to be added to assign.
|
||||
$field = new xmldb_field('multimarkmethod', XMLDB_TYPE_CHAR, '10', null, false, false, null, 'markercount');
|
||||
// Conditionally launch add field multimarkmethod.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define table assign_mark to be created.
|
||||
$table = new xmldb_table('assign_mark');
|
||||
|
||||
// Adding fields to table assign_mark.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('assignment', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('gradeid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('marker', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('mark', XMLDB_TYPE_NUMBER, '10,5', null, false, null, null);
|
||||
$table->add_field('workflowstate', XMLDB_TYPE_CHAR, '20', null, null, null, null);
|
||||
|
||||
// Adding keys to table assign_grades_mark.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('assignment', XMLDB_KEY_FOREIGN, ['assignment'], 'assign', ['id']);
|
||||
$table->add_key('gradeid', XMLDB_KEY_FOREIGN, ['gradeid'], 'assign_grades', ['id']);
|
||||
$table->add_key('marker', XMLDB_KEY_FOREIGN, ['marker'], 'user', ['id']);
|
||||
|
||||
// Conditionally launch create table for assign_mark.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table assign_allocated_marker to be created.
|
||||
$table = new xmldb_table('assign_allocated_marker');
|
||||
|
||||
// Adding fields to table assign_allocated_marker.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('student', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('assignment', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('marker', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table assign_allocated_marker.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('student', XMLDB_KEY_FOREIGN, ['student'], 'user', ['id']);
|
||||
$table->add_key('assignment', XMLDB_KEY_FOREIGN, ['assignment'], 'assign', ['id']);
|
||||
$table->add_key('marker', XMLDB_KEY_FOREIGN, ['marker'], 'user', ['id']);
|
||||
|
||||
// Conditionally launch create table for assign_allocated_marker.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define field allocatedmarker to be dropped from assign_user_flags.
|
||||
$table = new xmldb_table('assign_user_flags');
|
||||
$field = new xmldb_field('allocatedmarker');
|
||||
|
||||
// Populate assign_allocated_marker before the allocatedmarker field is dropped.
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$DB->execute(
|
||||
"INSERT INTO {assign_allocated_marker} (assignment, student, marker)
|
||||
SELECT assignment, userid, allocatedmarker
|
||||
FROM {assign_user_flags}"
|
||||
);
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
// Define field multimarkrounding to be added to assign.
|
||||
$table = new xmldb_table('assign');
|
||||
$field = new xmldb_field('multimarkrounding', XMLDB_TYPE_INTEGER, '2', null, false, false, null, 'multimarkmethod');
|
||||
// Conditionally launch add field multimarkrounding.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Assign savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2026022301, 'assign');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -358,6 +358,9 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
'm.maxattempts, ' .
|
||||
'm.markingworkflow, ' .
|
||||
'm.markingallocation, ' .
|
||||
'm.markercount, ' .
|
||||
'm.multimarkmethod, ' .
|
||||
'm.multimarkrounding, ' .
|
||||
'm.markinganonymous, ' .
|
||||
'm.requiresubmissionstatement, '.
|
||||
'm.preventsubmissionnotingroup, '.
|
||||
@@ -438,6 +441,9 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
'maxattempts' => $module->maxattempts,
|
||||
'markingworkflow' => $module->markingworkflow,
|
||||
'markingallocation' => $module->markingallocation,
|
||||
'markercount' => $module->markercount,
|
||||
'multimarkmethod' => $module->multimarkmethod,
|
||||
'multimarkrounding' => $module->multimarkrounding,
|
||||
'markinganonymous' => $module->markinganonymous,
|
||||
'requiresubmissionstatement' => $module->requiresubmissionstatement,
|
||||
'preventsubmissionnotingroup' => $module->preventsubmissionnotingroup,
|
||||
@@ -558,6 +564,9 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
'maxattempts' => new external_value(PARAM_INT, 'maximum number of attempts allowed'),
|
||||
'markingworkflow' => new external_value(PARAM_INT, 'enable marking workflow'),
|
||||
'markingallocation' => new external_value(PARAM_INT, 'enable marking allocation'),
|
||||
'markercount' => new external_value(PARAM_INT, 'number of markers'),
|
||||
'multimarkmethod' => new external_value(PARAM_TEXT, 'method to use when combining marks to a final grade'),
|
||||
'multimarkrounding' => new external_value(PARAM_INT, 'rounding direction to use when handling decimal marks'),
|
||||
'markinganonymous' => new external_value(PARAM_INT, 'enable marking anonymous'),
|
||||
'requiresubmissionstatement' => new external_value(PARAM_INT, 'student must accept submission statement'),
|
||||
'preventsubmissionnotingroup' => new external_value(PARAM_INT, 'Prevent submission not in group', VALUE_OPTIONAL),
|
||||
@@ -1127,7 +1136,7 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
|
||||
|
||||
$sql = "SELECT auf.id,auf.assignment,auf.userid,auf.locked,auf.mailed,".
|
||||
"auf.extensionduedate,auf.workflowstate,auf.allocatedmarker ".
|
||||
"auf.extensionduedate,auf.workflowstate " .
|
||||
"FROM {assign_user_flags} auf ".
|
||||
"WHERE auf.assignment ".$inorequalsql.
|
||||
" ORDER BY auf.assignment, auf.id";
|
||||
@@ -1143,7 +1152,6 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
$userflag['mailed'] = $rd->mailed;
|
||||
$userflag['extensionduedate'] = $rd->extensionduedate;
|
||||
$userflag['workflowstate'] = $rd->workflowstate;
|
||||
$userflag['allocatedmarker'] = $rd->allocatedmarker;
|
||||
|
||||
if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
|
||||
if (!is_null($assignment)) {
|
||||
@@ -1197,7 +1205,6 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
'mailed' => new external_value(PARAM_INT, 'mailed'),
|
||||
'extensionduedate' => new external_value(PARAM_INT, 'extension due date'),
|
||||
'workflowstate' => new external_value(PARAM_ALPHA, 'marking workflow state', VALUE_OPTIONAL),
|
||||
'allocatedmarker' => new external_value(PARAM_INT, 'allocated marker')
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1545,7 +1552,8 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
array(
|
||||
'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
|
||||
'userid' => new external_value(PARAM_INT, 'The user id the submission belongs to'),
|
||||
'jsonformdata' => new external_value(PARAM_RAW, 'The data from the grading form, encoded as a json array')
|
||||
'jsonformdata' => new external_value(PARAM_RAW, 'The data from the grading form, encoded as a json array'),
|
||||
'marker' => new external_value(PARAM_BOOL, 'Flag, false if grading, true if marking', VALUE_DEFAULT, false),
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1556,12 +1564,12 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
* @param int $assignmentid The id of the assignment
|
||||
* @param int $userid The id of the user the submission belongs to.
|
||||
* @param string $jsonformdata The data from the form, encoded as a json array.
|
||||
* @param bool $marker Are we marking instead of grading?
|
||||
* @return array of warnings to indicate any errors.
|
||||
* @since Moodle 3.1
|
||||
*/
|
||||
public static function submit_grading_form($assignmentid, $userid, $jsonformdata) {
|
||||
public static function submit_grading_form($assignmentid, $userid, $jsonformdata, $marker = false) {
|
||||
global $CFG, $USER;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/assign/locallib.php');
|
||||
require_once($CFG->dirroot . '/mod/assign/gradeform.php');
|
||||
|
||||
@@ -1569,7 +1577,8 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
array(
|
||||
'assignmentid' => $assignmentid,
|
||||
'userid' => $userid,
|
||||
'jsonformdata' => $jsonformdata
|
||||
'marker' => $marker,
|
||||
'jsonformdata' => $jsonformdata,
|
||||
));
|
||||
|
||||
list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
|
||||
@@ -1585,7 +1594,8 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
'userid' => $params['userid'],
|
||||
'attemptnumber' => $data['attemptnumber'],
|
||||
'rownum' => 0,
|
||||
'gradingpanel' => true
|
||||
'gradingpanel' => true,
|
||||
'marker' => $marker,
|
||||
);
|
||||
|
||||
if (WS_SERVER) {
|
||||
@@ -1600,7 +1610,6 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
// Data is injected into the form by the last param for the constructor.
|
||||
$mform = new mod_assign_grade_form(null, $formparams, 'post', '', null, true, $data);
|
||||
$validateddata = $mform->get_data();
|
||||
|
||||
if ($validateddata) {
|
||||
$assignment->save_grade($params['userid'], $validateddata);
|
||||
} else {
|
||||
@@ -2637,8 +2646,13 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
'onlyids' => new external_value(PARAM_BOOL, 'Do not return all user fields', VALUE_DEFAULT, false),
|
||||
'includeenrolments' => new external_value(PARAM_BOOL, 'Do return courses where the user is enrolled',
|
||||
VALUE_DEFAULT, true),
|
||||
'tablesort' => new external_value(PARAM_BOOL, 'Apply current user table sorting preferences.',
|
||||
VALUE_DEFAULT, false)
|
||||
'tablesort' => new external_value(
|
||||
PARAM_BOOL,
|
||||
'Apply current user table sorting preferences.',
|
||||
VALUE_DEFAULT,
|
||||
false
|
||||
),
|
||||
'marking' => new external_value(PARAM_BOOL, 'Are we marking instead of grading?', VALUE_DEFAULT, false),
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -2654,12 +2668,22 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
* @param bool $onlyids Only return user ids.
|
||||
* @param bool $includeenrolments Return courses where the user is enrolled.
|
||||
* @param bool $tablesort Apply current user table sorting params from the grading table.
|
||||
* @param bool $marking Are we marking instead of grading?
|
||||
* @return array of warnings and status result
|
||||
* @since Moodle 3.1
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function list_participants($assignid, $groupid, $filter, $skip,
|
||||
$limit, $onlyids, $includeenrolments, $tablesort) {
|
||||
public static function list_participants(
|
||||
$assignid,
|
||||
$groupid,
|
||||
$filter,
|
||||
$skip,
|
||||
$limit,
|
||||
$onlyids,
|
||||
$includeenrolments,
|
||||
$tablesort,
|
||||
$marking
|
||||
) {
|
||||
global $DB, $CFG;
|
||||
require_once($CFG->dirroot . "/mod/assign/locallib.php");
|
||||
require_once($CFG->dirroot . "/user/lib.php");
|
||||
@@ -2674,7 +2698,8 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
'limit' => $limit,
|
||||
'onlyids' => $onlyids,
|
||||
'includeenrolments' => $includeenrolments,
|
||||
'tablesort' => $tablesort
|
||||
'tablesort' => $tablesort,
|
||||
'marking' => $marking,
|
||||
));
|
||||
$warnings = array();
|
||||
|
||||
@@ -2682,6 +2707,7 @@ class mod_assign_external extends \mod_assign\external\external_api {
|
||||
|
||||
require_capability('mod/assign:view', $context);
|
||||
|
||||
$assign->set_is_marking($marking);
|
||||
$assign->require_view_grades();
|
||||
|
||||
$participants = array();
|
||||
|
||||
@@ -151,8 +151,7 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
$fields .= 'uf.mailed as mailed, ';
|
||||
$fields .= 'uf.locked as locked, ';
|
||||
$fields .= 'uf.extensionduedate as extensionduedate, ';
|
||||
$fields .= 'uf.workflowstate as workflowstate, ';
|
||||
$fields .= 'uf.allocatedmarker as allocatedmarker';
|
||||
$fields .= 'uf.workflowstate as workflowstate';
|
||||
|
||||
$from = '{user} u
|
||||
LEFT JOIN {assign_submission} s
|
||||
@@ -346,10 +345,14 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
// Check to see if marker filter is set.
|
||||
$markerfilter = (int)get_user_preferences('assign_markerfilter', '');
|
||||
if (!empty($markerfilter)) {
|
||||
$from .= 'LEFT JOIN {assign_allocated_marker} am
|
||||
ON u.id = am.student
|
||||
AND am.assignment = :assignmentid4 ';
|
||||
$params['assignmentid4'] = (int)$this->assignment->get_instance()->id;
|
||||
if ($markerfilter == ASSIGN_MARKER_FILTER_NO_MARKER) {
|
||||
$where .= ' AND (uf.allocatedmarker IS NULL OR uf.allocatedmarker = 0)';
|
||||
$where .= ' AND am.marker IS NULL';
|
||||
} else {
|
||||
$where .= ' AND uf.allocatedmarker = :markerid';
|
||||
$where .= " AND am.marker = :markerid";
|
||||
$params['markerid'] = $markerfilter;
|
||||
}
|
||||
}
|
||||
@@ -453,14 +456,21 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
$columns[] = 'team';
|
||||
$headers[] = get_string('submissionteam', 'assign');
|
||||
}
|
||||
// Allocated marker.
|
||||
|
||||
// Multiple markers.
|
||||
if ($this->assignment->get_instance()->markingworkflow &&
|
||||
$this->assignment->get_instance()->markingallocation &&
|
||||
has_capability('mod/assign:manageallocations', $this->assignment->get_context())) {
|
||||
// Add a column for the allocated marker.
|
||||
$columns[] = 'allocatedmarker';
|
||||
$headers[] = get_string('marker', 'assign');
|
||||
property_exists(
|
||||
$assignment->get_instance(),
|
||||
'markercount',
|
||||
)
|
||||
) {
|
||||
for ($i = 1; $i <= $assignment->get_instance()->markercount; $i++) {
|
||||
$columns[] = "marker$i";
|
||||
$headers[] = get_string('markernumber', 'assign', $i);
|
||||
}
|
||||
}
|
||||
|
||||
// Grade.
|
||||
$columns[] = 'grade';
|
||||
$headers[] = get_string('gradenoun');
|
||||
@@ -665,15 +675,6 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
$o .= html_writer::div($allworkflowstates[$workflowstate]);
|
||||
} else {
|
||||
$o .= html_writer::select($workflowstates, $name, $workflowstate, ['' => $notmarked]);
|
||||
// Check if this user is a marker that can't manage allocations and doesn't have the marker column added.
|
||||
if ($this->assignment->get_instance()->markingworkflow &&
|
||||
$this->assignment->get_instance()->markingallocation &&
|
||||
!has_capability('mod/assign:manageallocations', $this->assignment->get_context())) {
|
||||
|
||||
$name = 'quickgrade_' . $row->id . '_allocatedmarker';
|
||||
$o .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => $name,
|
||||
'value' => $row->allocatedmarker]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$o .= $this->output->container(get_string('markingworkflowstate' . $workflowstate, 'assign'), $workflowstate);
|
||||
@@ -696,56 +697,102 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
return get_string('markingworkflowstate' . $state, 'assign');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user object for the marker of a given student and marker number.
|
||||
*
|
||||
* @param int $studentid ID of the student.
|
||||
* @param int $number Marker index number.
|
||||
* @return stdClass|false User object or false if not found.
|
||||
*/
|
||||
protected function get_marker_number(int $studentid, int $number): stdClass|false {
|
||||
global $DB;
|
||||
$multimarkers = $DB->get_fieldset('assign_allocated_marker', 'marker', [
|
||||
'student' => $studentid, 'assignment' => $this->assignment->get_instance()->id,
|
||||
]);
|
||||
if (!empty($multimarkers) && count($multimarkers) >= $number) {
|
||||
// Then get the name of the one at the column position requested, e.g. marker1, marker2, etc...
|
||||
$index = $number - 1;
|
||||
return \core_user::get_user($multimarkers[$index]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* list current marker
|
||||
*
|
||||
* @param stdClass $row - The row of data
|
||||
* @return id the user->id of the marker.
|
||||
* @param int $markerpos - Marker position number - related to the markercount
|
||||
* @return string The name of the allocated marker
|
||||
*/
|
||||
public function col_allocatedmarker(stdClass $row) {
|
||||
public function col_allocatedmarker(stdClass $row, int $markerpos = 1) {
|
||||
static $markers = null;
|
||||
static $markerlist = array();
|
||||
if ($markers === null) {
|
||||
list($sort, $params) = users_order_by_sql('u');
|
||||
// Only enrolled users could be assigned as potential markers.
|
||||
$markers = get_enrolled_users($this->assignment->get_context(), 'mod/assign:grade', 0, 'u.*', $sort);
|
||||
$markerlist[0] = get_string('choosemarker', 'assign');
|
||||
$viewfullnames = has_capability('moodle/site:viewfullnames', $this->assignment->get_context());
|
||||
foreach ($markers as $marker) {
|
||||
$markerlist[$marker->id] = fullname($marker, $viewfullnames);
|
||||
|
||||
// Get the allocated markers that have been assigned to this student, if we are using multi-marking.
|
||||
$allocatedmarker = $this->get_marker_number($row->userid, $markerpos);
|
||||
|
||||
if ($this->is_downloading()) {
|
||||
if ($allocatedmarker) {
|
||||
return fullname(
|
||||
$allocatedmarker,
|
||||
has_capability('moodle/site:viewfullnames', $this->assignment->get_context())
|
||||
);
|
||||
}
|
||||
}
|
||||
if (empty($markerlist)) {
|
||||
// TODO: add some form of notification here that no markers are available.
|
||||
return '';
|
||||
}
|
||||
if ($this->is_downloading()) {
|
||||
if (isset($markers[$row->allocatedmarker])) {
|
||||
return fullname($markers[$row->allocatedmarker],
|
||||
has_capability('moodle/site:viewfullnames', $this->assignment->get_context()));
|
||||
} else {
|
||||
|
||||
if (
|
||||
$this->quickgrading &&
|
||||
has_capability('mod/assign:manageallocations', $this->assignment->get_context()) &&
|
||||
(
|
||||
empty($row->workflowstate) ||
|
||||
$row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_INMARKING ||
|
||||
$row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED
|
||||
)
|
||||
) {
|
||||
// Get the potential users who could be assigned as an allocated marker.
|
||||
if ($markers === null) {
|
||||
[$sort, $params] = users_order_by_sql('u');
|
||||
// Only enrolled users could be assigned as potential markers.
|
||||
$markers = get_enrolled_users($this->assignment->get_context(), 'mod/assign:grade', 0, 'u.*', $sort);
|
||||
$markerlist[0] = get_string('choosemarker', 'assign');
|
||||
$viewfullnames = has_capability('moodle/site:viewfullnames', $this->assignment->get_context());
|
||||
foreach ($markers as $marker) {
|
||||
$markerlist[$marker->id] = fullname($marker, $viewfullnames);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($markerlist)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (count($markers) > 1) {
|
||||
$label = get_string('allocatedmarker', 'assign') . ' ' . $markerpos;
|
||||
} else {
|
||||
$label = get_string('allocatedmarker', 'assign');
|
||||
}
|
||||
|
||||
$name = 'quickgrade_' . $row->id . '_allocatedmarker_' . $markerpos;
|
||||
return html_writer::label(
|
||||
$label,
|
||||
'menu' . $name
|
||||
) . html_writer::select($markerlist, $name, ($allocatedmarker) ? $allocatedmarker->id : '', false);
|
||||
}
|
||||
|
||||
if ($this->quickgrading && has_capability('mod/assign:manageallocations', $this->assignment->get_context()) &&
|
||||
(empty($row->workflowstate) ||
|
||||
$row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_INMARKING ||
|
||||
$row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED)) {
|
||||
|
||||
$name = 'quickgrade_' . $row->id . '_allocatedmarker';
|
||||
return html_writer::select($markerlist, $name, $row->allocatedmarker, false);
|
||||
} else if (!empty($row->allocatedmarker)) {
|
||||
if ($allocatedmarker) {
|
||||
$output = '';
|
||||
if ($this->quickgrading) { // Add hidden field for quickgrading page.
|
||||
$name = 'quickgrade_' . $row->id . '_allocatedmarker';
|
||||
$attributes = ['type' => 'hidden', 'name' => $name, 'value' => $row->allocatedmarker];
|
||||
$name = 'quickgrade_' . $row->id . '_allocatedmarker_' . $markerpos;
|
||||
$attributes = ['type' => 'hidden', 'name' => $name, 'value' => $allocatedmarker->id];
|
||||
$output .= html_writer::empty_tag('input', $attributes);
|
||||
}
|
||||
$output .= $markerlist[$row->allocatedmarker];
|
||||
$output .= html_writer::tag('strong', fullname($allocatedmarker));
|
||||
return $output;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* For download only - list all the valid options for this custom scale.
|
||||
*
|
||||
@@ -774,9 +821,10 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
* @param int $userid The user id of the user this grade belongs to
|
||||
* @param int $modified Timestamp showing when the grade was last modified
|
||||
* @param float $deductedmark The deducted mark if penalty is applied
|
||||
* @param int|null $markerid The allocated marker id if we are displaying a mark instead of an overall grade
|
||||
* @return string The formatted grade
|
||||
*/
|
||||
public function display_grade($grade, $editable, $userid, $modified, float $deductedmark = 0) {
|
||||
public function display_grade($grade, $editable, $userid, $modified, float $deductedmark = 0, ?int $markerid = null) {
|
||||
if ($this->is_downloading()) {
|
||||
if ($this->assignment->get_instance()->grade >= 0) {
|
||||
if ($grade == -1 || $grade === null) {
|
||||
@@ -793,7 +841,7 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
return $scale;
|
||||
}
|
||||
}
|
||||
return $this->assignment->display_grade($grade, $editable, $userid, $modified, $deductedmark);
|
||||
return $this->assignment->display_grade($grade, $editable, $userid, $modified, $deductedmark, $markerid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1003,10 +1051,29 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
* @return string
|
||||
*/
|
||||
public function col_grade(stdClass $row): string {
|
||||
global $DB, $USER;
|
||||
|
||||
$gradingdisabled = $this->assignment->grading_disabled($row->id, true, $this->gradinginfo);
|
||||
$displaygrade = $this->display_grade($row->grade, $this->quickgrading && !$gradingdisabled, $row->userid, $row->timemarked);
|
||||
|
||||
if (!$this->is_downloading() && $this->hasgrade) {
|
||||
// If assignment uses multiple markers with the manual (aka agreement)
|
||||
// method then the grader must be an existing marker so only show the
|
||||
// action menu if they are.
|
||||
$multimarkcangrade = true;
|
||||
if (
|
||||
$this->assignment->is_using_multiple_marking() &&
|
||||
$this->assignment->get_instance()->multimarkmethod === 'manual' &&
|
||||
!$DB->get_record('assign_mark', ['gradeid' => $row->gradeid, 'marker' => $USER->id]) &&
|
||||
!$DB->get_record('assign_allocated_marker', [
|
||||
'assignment' => $this->assignment->get_instance()->id,
|
||||
'marker' => $USER->id,
|
||||
'student' => $row->userid,
|
||||
])
|
||||
) {
|
||||
$multimarkcangrade = false;
|
||||
}
|
||||
|
||||
if (!$this->is_downloading() && $this->hasgrade && $multimarkcangrade) {
|
||||
$urlparams = [
|
||||
'id' => $this->assignment->get_course_module()->id,
|
||||
'rownum' => 0,
|
||||
@@ -1045,6 +1112,128 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
return $displaygrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the marker column's data for display.
|
||||
*
|
||||
* @param stdClass $row Row object.
|
||||
* @param int $col Marker number column.
|
||||
* @return string
|
||||
*/
|
||||
public function col_marker(stdClass $row, int $col): string {
|
||||
global $USER, $DB;
|
||||
$allocatedmarker = "";
|
||||
$index = $col - 1;
|
||||
|
||||
if (
|
||||
$this->assignment->get_instance()->markingworkflow &&
|
||||
$this->assignment->get_instance()->markingallocation
|
||||
) {
|
||||
$allocatedmarker = $this->col_allocatedmarker($row, $col);
|
||||
}
|
||||
|
||||
$gradingdisabled = $this->assignment->grading_disabled($row->id, true, $this->gradinginfo);
|
||||
$displaymark = "";
|
||||
|
||||
if (!$this->is_downloading() && $this->hasgrade) {
|
||||
if (
|
||||
$this->assignment->get_instance()->markingworkflow &&
|
||||
$this->assignment->get_instance()->markingallocation
|
||||
) {
|
||||
// Allocated markers are enabled: get the mark corresponding to
|
||||
// the marker for this column.
|
||||
$markers = array_values($DB->get_records('assign_allocated_marker', [
|
||||
'student' => $row->userid,
|
||||
'assignment' => $this->assignment->get_instance()->id,
|
||||
], 'id'));
|
||||
if (count($markers) > $index) {
|
||||
$mark = $DB->get_record('assign_mark', ['gradeid' => $row->gradeid, 'marker' => $markers[$index]->marker]);
|
||||
// Mark is only editable if we are quick grading, grading is not disabled, and if we are either
|
||||
// the marker for this column, or we have manageallocations permissions.
|
||||
$editable = (
|
||||
($this->quickgrading) &&
|
||||
(!$gradingdisabled) &&
|
||||
($USER->id == $markers[$index]->marker)
|
||||
);
|
||||
$displaymark = $this->display_grade(
|
||||
$mark->mark ?? null,
|
||||
$editable,
|
||||
$row->userid,
|
||||
$row->timemarked,
|
||||
0,
|
||||
$markers[$index]->marker,
|
||||
);
|
||||
// Display the workflow state for this mark.
|
||||
if ($markers[$index]->marker > 0) {
|
||||
$displaymark .= html_writer::div(
|
||||
get_string('markingworkflowstate' . ($mark->workflowstate ?? 'notmarked'), 'assign'),
|
||||
'badge bg-info d-block'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$urlparams = [
|
||||
'id' => $this->assignment->get_course_module()->id,
|
||||
'rownum' => 0,
|
||||
'action' => 'marker',
|
||||
];
|
||||
|
||||
if ($this->assignment->is_blind_marking()) {
|
||||
if (empty($row->recordid)) {
|
||||
$row->recordid = $this->assignment->get_uniqueid_for_user($row->userid);
|
||||
}
|
||||
$urlparams['blindid'] = $row->recordid;
|
||||
} else {
|
||||
$urlparams['userid'] = $row->userid;
|
||||
}
|
||||
$url = new moodle_url('/mod/assign/view.php', $urlparams);
|
||||
|
||||
// The container with the grade information.
|
||||
$gradecontainer = $this->output->container($displaymark, 'w-100');
|
||||
|
||||
// Should this user get the 'Mark' action menu item?
|
||||
$isallocatedmarker = false;
|
||||
|
||||
if (
|
||||
$this->assignment->get_instance()->markingworkflow &&
|
||||
$this->assignment->get_instance()->markingallocation
|
||||
) {
|
||||
// If allocated marking is enabled is this user the marker for this column?
|
||||
if (
|
||||
$markers = $DB->get_fieldset('assign_allocated_marker', 'marker', [
|
||||
'student' => $row->userid,
|
||||
'assignment' => $this->assignment->get_instance()->id,
|
||||
])
|
||||
) {
|
||||
$isallocatedmarker = (array_key_exists($index, $markers) && $markers[$index] == $USER->id);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isallocatedmarker) {
|
||||
$menu = new action_menu();
|
||||
$menu->set_owner_selector('.gradingtable-actionmenu');
|
||||
$menu->set_boundary('window');
|
||||
$menu->set_kebab_trigger(get_string('markactions', 'assign'));
|
||||
$menu->set_additional_classes('ps-2 ms-auto');
|
||||
// Prioritise the menu ahead of all other actions.
|
||||
$menu->prioritise = true;
|
||||
// Add the 'Mark' action item to the contextual menu.
|
||||
$menu->add(new action_menu_link_secondary($url, null, get_string('markverb', 'assign')));
|
||||
// The contextual menu container.
|
||||
$contextualmenucontainer = $this->output->container($this->output->render($menu), 'd-flex');
|
||||
return $allocatedmarker .
|
||||
$this->output->container($gradecontainer . $contextualmenucontainer, ['class' => 'd-flex']);
|
||||
}
|
||||
}
|
||||
// The table data is being downloaded, or the user cannot grade; therefore, only the formatted grade for display
|
||||
// is returned.
|
||||
if ($this->is_downloading()) {
|
||||
return $displaymark;
|
||||
} else {
|
||||
return $allocatedmarker . $this->output->container($gradecontainer, ['class' => 'd-flex']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a column of data for display.
|
||||
*
|
||||
@@ -1641,6 +1830,12 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
* @return mixed string or NULL
|
||||
*/
|
||||
public function other_cols($colname, $row) {
|
||||
// If the column name is formatted as "marker" and a number, e.g. "marker1", this is a Marker column
|
||||
// and so we need to call col_marker() instead with the marker number provided in the name. In this case "1".
|
||||
if (str_starts_with($colname, 'marker') && ($col = substr($colname, 6)) && ctype_digit($col)) {
|
||||
return $this->col_marker($row, $col);
|
||||
}
|
||||
|
||||
// For extra user fields the result is already in $row.
|
||||
if (empty($this->plugincache[$colname])) {
|
||||
return parent::other_cols($colname, $row);
|
||||
|
||||
@@ -374,6 +374,7 @@ $string['introattachments'] = 'Additional files';
|
||||
$string['introattachments_help'] = 'Additional files for use in the assignment, such as answer templates, may be added.';
|
||||
$string['invalidgradeforscale'] = 'The grade supplied was not valid for the current scale';
|
||||
$string['invalidfloatforgrade'] = 'The grade provided could not be understood: {$a}';
|
||||
$string['invalidmarkerallocation:notenoughmarkers'] = 'There are not enough markers ({$a->markers}) to meet the required number for this assignment ({$a->requested}).';
|
||||
$string['invalidoverrideid'] = 'Invalid override id';
|
||||
$string['lastmodifiedsubmission'] = 'Last modified (submission)';
|
||||
$string['lastmodifiedgrade'] = 'Last modified (grade)';
|
||||
@@ -384,9 +385,20 @@ $string['locksubmissionforstudent'] = 'Prevent any more submissions for student:
|
||||
$string['locksubmissions'] = 'Lock submissions';
|
||||
$string['manageassignfeedbackplugins'] = 'Manage assignment feedback plugins';
|
||||
$string['manageassignsubmissionplugins'] = 'Manage assignment submission plugins';
|
||||
$string['markactions'] = 'Mark actions';
|
||||
$string['markerallocations'] = 'Marker allocations';
|
||||
$string['marker'] = 'Marker';
|
||||
$string['markercount'] = 'Markers';
|
||||
$string['markercount_help'] = 'Number of markers for the assignment.';
|
||||
$string['markerfilter'] = 'Marker filter';
|
||||
$string['markerfilternomarker'] = 'No marker';
|
||||
$string['markernumber'] = 'Marker {$a}';
|
||||
$string['markgradeaverage'] = 'Average mark';
|
||||
$string['markgradeaverage_help'] = 'Grade will be calculated as the average of all marks given.';
|
||||
$string['markgrademanual'] = 'Manual';
|
||||
$string['markgrademanual_help'] = 'Grade will need to be manually entered, it won\'t be calculated automatically.';
|
||||
$string['markgrademaximum'] = 'Maximum mark';
|
||||
$string['markgrademaximum_help'] = 'Grade will be calculated as the highest of the marks given.';
|
||||
$string['markingallocation'] = 'Use marking allocation';
|
||||
$string['markingallocation_help'] = 'If enabled together with marking workflow, markers can be allocated to particular students.';
|
||||
$string['markinganonymous'] = 'Allow partial release of grades while marking anonymously';
|
||||
@@ -409,6 +421,22 @@ $string['markingworkflowstatenotmarked'] = 'Not marked';
|
||||
$string['markingworkflowstatereadyforreview'] = 'Marking completed';
|
||||
$string['markingworkflowstatereadyforrelease'] = 'Ready for release';
|
||||
$string['markingworkflowstatereleased'] = 'Released';
|
||||
$string['markoutof'] = 'Mark out of {$a}';
|
||||
$string['markoutof_help'] = 'Enter the mark for the student\'s submission here. You may include decimals.';
|
||||
$string['marks'] = 'Marks';
|
||||
$string['markverb'] = 'Mark';
|
||||
$string['multimarkmethod'] = 'Calculate grade';
|
||||
$string['multimarkmethod_help'] = 'How the grade will be calculated when there are multiple markers';
|
||||
$string['multimarkrounding'] = 'Rounding';
|
||||
$string['multimarkrounding_help'] = 'Whether to round up, down, naturally, or not at all when calculating an average mark';
|
||||
$string['multimarkrounding:down'] = 'Round down';
|
||||
$string['multimarkrounding:down_help'] = 'Average mark will be rounded down to the nearest whole number.';
|
||||
$string['multimarkrounding:natural'] = 'Natural rounding';
|
||||
$string['multimarkrounding:natural_help'] = 'Average mark will be rounded up or down naturally to the nearest whole number.';
|
||||
$string['multimarkrounding:none'] = 'No rounding';
|
||||
$string['multimarkrounding:none_help'] = 'Average mark will not be rounded.';
|
||||
$string['multimarkrounding:up'] = 'Round up';
|
||||
$string['multimarkrounding:up_help'] = 'Average mark will be rounded up to the nearest whole number.';
|
||||
$string['maxattempts'] = 'Allowed attempts';
|
||||
$string['maxattempts_help'] = 'The maximum number of submission attempts that can be made by a student. After this number has been reached, the submission can no longer be reopened.';
|
||||
$string['maxgrade'] = 'Maximum grade';
|
||||
@@ -500,10 +528,12 @@ $string['previoususer'] = 'Previous user';
|
||||
$string['privacy:attemptpath'] = 'attempt {$a}';
|
||||
$string['privacy:blindmarkingidentifier'] = 'The identifier used for anonymous submissions';
|
||||
$string['privacy:gradepath'] = 'grade';
|
||||
$string['privacy:metadata:assignallocatedmarker'] = 'Stores mappings between allocated markers and student assignments';
|
||||
$string['privacy:metadata:assigndownloadasfolders'] = 'A user preference for whether multiple file submissions should be downloaded into folders';
|
||||
$string['privacy:metadata:assignfeedbackpluginsummary'] = 'Feedback data for the assignment.';
|
||||
$string['privacy:metadata:assignfilter'] = 'Filter options such as \'Submitted\', \'Not submitted\', \'Requires grading\', and \'Granted extension\'';
|
||||
$string['privacy:metadata:assigngrades'] = 'Stores user grades for the assignment';
|
||||
$string['privacy:metadata:assignmark'] = 'Stores user marks for the assignment';
|
||||
$string['privacy:metadata:assignmarkerfilter'] = 'Filter the assign summary by the assigned marker.';
|
||||
$string['privacy:metadata:assignmentid'] = 'Assignment ID';
|
||||
$string['privacy:metadata:assignmessageexplanation'] = 'Messages are sent to students through the messaging system.';
|
||||
@@ -709,6 +739,7 @@ $string['usersnone'] = 'There are no users enrolled in this course or no users c
|
||||
$string['usersubmissioncannotberemoved'] = 'The submission of {$a} cannot be removed.';
|
||||
$string['userswhoneedtosubmit'] = 'Users who need to submit: {$a}';
|
||||
$string['usergrade'] = 'User grade';
|
||||
$string['usermark'] = 'User mark';
|
||||
$string['validmarkingworkflowstates'] = 'Valid marking workflow states';
|
||||
$string['viewadifferentattempt'] = 'View a different attempt';
|
||||
$string['viewbatchsetmarkingworkflowstate'] = 'View batch set marking workflow state page.';
|
||||
@@ -727,6 +758,9 @@ $string['viewfull'] = 'View full';
|
||||
$string['viewsummary'] = 'View summary';
|
||||
$string['viewsubmissiongradingtable'] = 'View submission grading table.';
|
||||
$string['viewrevealidentitiesconfirm'] = 'View reveal student identities confirmation page.';
|
||||
$string['workflowcontext'] = 'Workflow context';
|
||||
$string['workflowcontext_help_grade'] = 'Apply the workflow state to the overall grade for the submission.';
|
||||
$string['workflowcontext_help_mark'] = 'Apply the workflow state to your mark, where you are an allocated marker.';
|
||||
$string['workflowfilter'] = 'Workflow filter';
|
||||
$string['xofy'] = '{$a->x} of {$a->y}';
|
||||
|
||||
|
||||
@@ -1461,7 +1461,8 @@ function mod_assign_output_fragment_gradingpanel($args) {
|
||||
$viewargs = array(
|
||||
'userid' => $userid,
|
||||
'attemptnumber' => $attemptnumber,
|
||||
'formdata' => $formdata
|
||||
'formdata' => $formdata,
|
||||
'marker' => $args['marker'],
|
||||
);
|
||||
|
||||
return $assign->view('gradingpanel', $viewargs);
|
||||
|
||||
+717
-103
File diff suppressed because it is too large
Load Diff
@@ -243,6 +243,82 @@ class mod_assign_mod_form extends moodleform_mod {
|
||||
$mform->addHelpButton('markingallocation', 'markingallocation', 'assign');
|
||||
$mform->hideIf('markingallocation', 'markingworkflow', 'eq', 0);
|
||||
|
||||
$name = get_string('markercount', 'assign');
|
||||
$markercount = range(1, ASSIGN_MULTIMARKING_MAX_MARKERS);
|
||||
$mform->addElement('select', 'markercount', $name, array_combine($markercount, $markercount));
|
||||
$mform->addHelpButton('markercount', 'markercount', 'assign');
|
||||
$mform->disabledIf('markercount', 'advancedgradingmethod_submissions', 'neq', '');
|
||||
$mform->hideIf('markercount', 'advancedgradingmethod_submissions', 'neq', '');
|
||||
$mform->hideIf('markercount', 'markingallocation', 'neq', '1');
|
||||
$mform->hideIf('markercount', 'markingworkflow', 'neq', '1');
|
||||
|
||||
$name = get_string('multimarkmethod', 'assign');
|
||||
$options = new core\output\choicelist();
|
||||
$options->add_option(
|
||||
ASSIGN_MULTIMARKING_METHOD_MANUAL,
|
||||
get_string('markgrade' . ASSIGN_MULTIMARKING_METHOD_MANUAL, 'assign'),
|
||||
[
|
||||
'description' => get_string('markgrade' . ASSIGN_MULTIMARKING_METHOD_MANUAL . '_help', 'assign'),
|
||||
]
|
||||
);
|
||||
$options->add_option(
|
||||
ASSIGN_MULTIMARKING_METHOD_MAX,
|
||||
get_string('markgrade' . ASSIGN_MULTIMARKING_METHOD_MAX, 'assign'),
|
||||
[
|
||||
'description' => get_string('markgrade' . ASSIGN_MULTIMARKING_METHOD_MAX . '_help', 'assign'),
|
||||
]
|
||||
);
|
||||
$options->add_option(
|
||||
ASSIGN_MULTIMARKING_METHOD_AVERAGE,
|
||||
get_string('markgrade' . ASSIGN_MULTIMARKING_METHOD_AVERAGE, 'assign'),
|
||||
[
|
||||
'description' => get_string('markgrade' . ASSIGN_MULTIMARKING_METHOD_AVERAGE . '_help', 'assign'),
|
||||
]
|
||||
);
|
||||
$mform->addElement('choicedropdown', 'multimarkmethod', $name, $options);
|
||||
$mform->hideIf('multimarkmethod', 'markingallocation', 'eq', '0');
|
||||
$mform->hideIf('multimarkmethod', 'markercount', 'eq', '1');
|
||||
$mform->disabledIf('multimarkmethod', 'advancedgradingmethod_submissions', 'neq', '');
|
||||
$mform->hideIf('multimarkmethod', 'advancedgradingmethod_submissions', 'neq', '');
|
||||
|
||||
$name = get_string('multimarkrounding', 'assign');
|
||||
$options = new core\output\choicelist();
|
||||
$options->add_option(
|
||||
ASSIGN_MULTIMARKING_AVERAGE_ROUND_NONE,
|
||||
get_string('multimarkrounding:none', 'assign'),
|
||||
[
|
||||
'description' => get_string('multimarkrounding:none_help', 'assign'),
|
||||
]
|
||||
);
|
||||
$options->add_option(
|
||||
ASSIGN_MULTIMARKING_AVERAGE_ROUND_NATURAL,
|
||||
get_string('multimarkrounding:natural', 'assign'),
|
||||
[
|
||||
'description' => get_string('multimarkrounding:natural_help', 'assign'),
|
||||
]
|
||||
);
|
||||
$options->add_option(
|
||||
ASSIGN_MULTIMARKING_AVERAGE_ROUND_DOWN,
|
||||
get_string('multimarkrounding:down', 'assign'),
|
||||
[
|
||||
'description' => get_string('multimarkrounding:down_help', 'assign'),
|
||||
]
|
||||
);
|
||||
$options->add_option(
|
||||
ASSIGN_MULTIMARKING_AVERAGE_ROUND_UP,
|
||||
get_string('multimarkrounding:up', 'assign'),
|
||||
[
|
||||
'description' => get_string('multimarkrounding:up_help', 'assign'),
|
||||
]
|
||||
);
|
||||
$mform->addElement('choicedropdown', 'multimarkrounding', $name, $options);
|
||||
$mform->hideIf('multimarkrounding', 'markingallocation', 'eq', '0');
|
||||
$mform->hideIf('multimarkrounding', 'markercount', 'eq', '1');
|
||||
$mform->hideIf('multimarkrounding', 'multimarkmethod', 'neq', 'average');
|
||||
$mform->disabledIf('multimarkrounding', 'multimarkmethod', 'neq', 'average');
|
||||
$mform->disabledIf('multimarkrounding', 'advancedgradingmethod_submissions', 'neq', '');
|
||||
$mform->hideIf('multimarkrounding', 'advancedgradingmethod_submissions', 'neq', '');
|
||||
|
||||
$name = get_string('markinganonymous', 'assign');
|
||||
$mform->addElement('selectyesno', 'markinganonymous', $name);
|
||||
$mform->addHelpButton('markinganonymous', 'markinganonymous', 'assign');
|
||||
|
||||
@@ -360,6 +360,62 @@ if ($ADMIN->fulltree) {
|
||||
$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
|
||||
$settings->add($setting);
|
||||
|
||||
// Default setting for number of markers.
|
||||
$name = new lang_string('markercount', 'mod_assign');
|
||||
$description = new lang_string('markercount_help', 'mod_assign');
|
||||
$setting = new admin_setting_configselect(
|
||||
'assign/markercount',
|
||||
$name,
|
||||
$description,
|
||||
ASSIGN_MULTIMARKING_DEFAULT_MARKERS,
|
||||
array_combine(
|
||||
range(1, ASSIGN_MULTIMARKING_MAX_MARKERS),
|
||||
range(1, ASSIGN_MULTIMARKING_MAX_MARKERS)
|
||||
),
|
||||
);
|
||||
$setting->set_advanced_flag_options(admin_setting_flag::ENABLED, false);
|
||||
$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
|
||||
$settings->add($setting);
|
||||
|
||||
// Default setting for multi-marking method (e.g. manual, max, etc...).
|
||||
$name = new lang_string('multimarkmethod', 'mod_assign');
|
||||
$description = new lang_string('multimarkmethod_help', 'mod_assign');
|
||||
$options = [
|
||||
ASSIGN_MULTIMARKING_METHOD_MANUAL => get_string('markgrade' . ASSIGN_MULTIMARKING_METHOD_MANUAL, 'assign'),
|
||||
ASSIGN_MULTIMARKING_METHOD_MAX => get_string('markgrade' . ASSIGN_MULTIMARKING_METHOD_MAX, 'assign'),
|
||||
ASSIGN_MULTIMARKING_METHOD_AVERAGE => get_string('markgrade' . ASSIGN_MULTIMARKING_METHOD_AVERAGE, 'assign'),
|
||||
];
|
||||
$setting = new admin_setting_configselect(
|
||||
'assign/multimarkmethod',
|
||||
$name,
|
||||
$description,
|
||||
ASSIGN_MULTIMARKING_METHOD_MANUAL,
|
||||
$options,
|
||||
);
|
||||
$setting->set_advanced_flag_options(admin_setting_flag::ENABLED, false);
|
||||
$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
|
||||
$settings->add($setting);
|
||||
|
||||
// Default setting for multi-marking rounding (e.g. up, down, none).
|
||||
$name = new lang_string('multimarkrounding', 'mod_assign');
|
||||
$description = new lang_string('multimarkrounding_help', 'mod_assign');
|
||||
$options = [
|
||||
ASSIGN_MULTIMARKING_AVERAGE_ROUND_NONE => get_string('multimarkrounding:none', 'assign'),
|
||||
ASSIGN_MULTIMARKING_AVERAGE_ROUND_NATURAL => get_string('multimarkrounding:natural', 'assign'),
|
||||
ASSIGN_MULTIMARKING_AVERAGE_ROUND_DOWN => get_string('multimarkrounding:down', 'assign'),
|
||||
ASSIGN_MULTIMARKING_AVERAGE_ROUND_UP => get_string('multimarkrounding:up', 'assign'),
|
||||
];
|
||||
$setting = new admin_setting_configselect(
|
||||
'assign/multimarkrounding',
|
||||
$name,
|
||||
$description,
|
||||
ASSIGN_MULTIMARKING_AVERAGE_ROUND_NONE,
|
||||
$options,
|
||||
);
|
||||
$setting->set_advanced_flag_options(admin_setting_flag::ENABLED, false);
|
||||
$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
|
||||
$settings->add($setting);
|
||||
|
||||
$name = new lang_string('markinganonymous', 'mod_assign');
|
||||
$description = new lang_string('markinganonymous_help', 'mod_assign');
|
||||
$setting = new admin_setting_configcheckbox('assign/markinganonymous',
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<a href="#previous" data-action="previous-user" aria-label="{{#str}} previoususer, mod_assign {{/str}}" title="{{#str}} previoususer, mod_assign {{/str}}">{{{larrow}}}</a>
|
||||
<span data-region="input-field">
|
||||
<label for="change-user-select" class="visually-hidden">{{#str}} changeuser, mod_assign {{/str}}</label>
|
||||
<select id="change-user-select" data-action="change-user" data-currentuserid="{{currentuserid}}" data-assignmentid="{{assignmentid}}" data-groupid="{{groupid}}"></select>
|
||||
<select id="change-user-select" data-action="change-user" data-currentuserid="{{currentuserid}}" data-assignmentid="{{assignmentid}}" data-groupid="{{groupid}}" data-ismarking="{{ismarking}}"></select>
|
||||
</span>
|
||||
<a href="#next" data-action="next-user" aria-label="{{#str}} nextuser, mod_assign {{/str}}" title="{{#str}} nextuser, mod_assign {{/str}}">{{{rarrow}}}</a>
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"gradelink": "https://moodle.org"
|
||||
"gradelink": "https://moodle.org",
|
||||
"marklink": "https://moodle.org"
|
||||
}
|
||||
|
||||
}}
|
||||
@@ -38,5 +39,10 @@
|
||||
<a class="btn btn-primary" href="{{gradelink}}">{{#str}}gradeverb, core{{/str}}</a>
|
||||
</div>
|
||||
{{/gradelink}}
|
||||
{{#marklink}}
|
||||
<div class="navitem">
|
||||
<a class="btn btn-primary" href="{{marklink}}">{{#str}}markverb, mod_assign{{/str}}</a>
|
||||
</div>
|
||||
{{/marklink}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,6 +127,9 @@ final class restore_date_test extends \restore_date_testcase {
|
||||
'timelimit' => DAYSECS,
|
||||
'submissionattachments' => 1,
|
||||
'gradepenalty' => 1,
|
||||
'markercount' => 5,
|
||||
'multimarkmethod' => 'average',
|
||||
'multimarkrounding' => 0,
|
||||
];
|
||||
$assign = $this->getDataGenerator()->create_module('assign', $record);
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
@mod @mod_assign @javascript
|
||||
Feature: Allocate markers to student submissions
|
||||
In order to use the marking workflow and record provisional marks before calculating a final grade
|
||||
As a teacher
|
||||
I need to allocate markers to student submissions
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | One | student1@example.com |
|
||||
| student2 | Student | Two | student2@example.com |
|
||||
| teacher1 | Teacher | One | teacher1@example.com |
|
||||
| teacher2 | Teacher | Two | teacher2@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher2 | C1 | editingteacher |
|
||||
And the following "activity" exists:
|
||||
| activity | assign |
|
||||
| course | C1 |
|
||||
| idnumber | A1 |
|
||||
| name | Assignment 1 |
|
||||
| section | 1 |
|
||||
| completion | 1 |
|
||||
| markingworkflow | 1 |
|
||||
| markingallocation | 1 |
|
||||
| markercount | 2 |
|
||||
| grade[modgrade_type] | point |
|
||||
| grade[modgrade_point] | 100 |
|
||||
|
||||
Scenario: Allocate markers to students via the Allocate Markers page
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I set the field "selectall" to "1"
|
||||
And I click on "Allocate marker" "button" in the "sticky-footer" "region"
|
||||
And I click on "Allocate marker" "button" in the ".modal-footer" "css_element"
|
||||
When I select "Teacher One" from the "Allocated marker 1" singleselect
|
||||
And I select "Teacher Two" from the "Allocated marker 2" singleselect
|
||||
And I press "Save changes"
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 |
|
||||
| Student One | Teacher One | Teacher Two |
|
||||
| Student Two | Teacher One | Teacher Two |
|
||||
|
||||
Scenario: Allocate markers to students via the Quick Grading page
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I click on "Quick grading" "checkbox"
|
||||
When I set the field "Allocated marker 1" in the "Student One" "table_row" to "Teacher One"
|
||||
And I set the field "Allocated marker 1" in the "Student Two" "table_row" to "Teacher One"
|
||||
And I set the field "Allocated marker 2" in the "Student One" "table_row" to "Teacher Two"
|
||||
And I set the field "Allocated marker 2" in the "Student Two" "table_row" to "Teacher Two"
|
||||
And I click on "Save" "button" in the "sticky-footer" "region"
|
||||
And I press "Continue"
|
||||
And I click on "Quick grading" "checkbox"
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 |
|
||||
| Student One | Teacher One | Teacher Two |
|
||||
| Student Two | Teacher One | Teacher Two |
|
||||
|
||||
Scenario: Allocate markers to students via the Grader page
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I go to "Student One" "Assignment 1" activity advanced grading page
|
||||
When I set the field "Marker 1" to "Teacher One"
|
||||
And I set the field "Marker 2" to "Teacher Two"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 |
|
||||
| Student One | Teacher One | Teacher Two |
|
||||
| Student Two | | |
|
||||
|
||||
Scenario: Allocate a duplicate marker to a student
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I set the field "selectall" to "1"
|
||||
And I click on "Allocate marker" "button" in the "sticky-footer" "region"
|
||||
And I click on "Allocate marker" "button" in the ".modal-footer" "css_element"
|
||||
When I select "Teacher One" from the "Allocated marker 1" singleselect
|
||||
And I select "Teacher One" from the "Allocated marker 2" singleselect
|
||||
And I press "Save changes"
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 |
|
||||
| Student One | Teacher One | |
|
||||
| Student Two | Teacher One | |
|
||||
And the following should not exist in the "submissions" table:
|
||||
| Marker 2 |
|
||||
| Teacher One |
|
||||
| Teacher One |
|
||||
@@ -0,0 +1,168 @@
|
||||
@mod @mod_assign @javascript
|
||||
Feature: Allocate marks to student submissions
|
||||
In order to assess a submission with multiple markers
|
||||
As a teacher
|
||||
I need to allocate marks to student submissions
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | One | student1@example.com |
|
||||
| student2 | Student | Two | student2@example.com |
|
||||
| teacher1 | Teacher | One | teacher1@example.com |
|
||||
| teacher2 | Teacher | Two | teacher2@example.com |
|
||||
| teacher3 | Teacher | Three | teacher3@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher2 | C1 | editingteacher |
|
||||
| teacher3 | C1 | editingteacher |
|
||||
And the following "activity" exists:
|
||||
| activity | assign |
|
||||
| course | C1 |
|
||||
| idnumber | A1 |
|
||||
| name | Assignment 1 |
|
||||
| section | 1 |
|
||||
| completion | 1 |
|
||||
| markingworkflow | 1 |
|
||||
| markingallocation | 1 |
|
||||
| markercount | 2 |
|
||||
| multimarkmethod | maximum |
|
||||
| grade[modgrade_type] | point |
|
||||
| grade[modgrade_point] | 100 |
|
||||
| assignfeedback_comments_enabled | 1 |
|
||||
And the following "mod_assign > marker_allocations" exist:
|
||||
| assign | user | marker |
|
||||
| Assignment 1 | student1 | teacher1 |
|
||||
| Assignment 1 | student1 | teacher2 |
|
||||
| Assignment 1 | student2 | teacher1 |
|
||||
| Assignment 1 | student2 | teacher2 |
|
||||
|
||||
Scenario: Allocate marks to students via the Quick Grading page
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I click on "Quick grading" "checkbox"
|
||||
When I set the field "User mark" in the "Student One" "table_row" to "99"
|
||||
And I click on "Save" "button" in the "sticky-footer" "region"
|
||||
And I press "Continue"
|
||||
And I click on "Quick grading" "checkbox"
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 |
|
||||
| Student One | 99 |
|
||||
|
||||
Scenario: Allocate marks to students via the Advanced Marker window
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I go to "Student One" "Assignment 1" activity advanced marking page
|
||||
When I set the field "Mark out of 100" to "50"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 |
|
||||
| Student One | 50 |
|
||||
|
||||
Scenario: Set workflow state for an allocated mark via Advanced Marker window
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I go to "Student One" "Assignment 1" activity advanced marking page
|
||||
When I set the field "Mark out of 100" to "42"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | Marking completed | Not marked | In marking |
|
||||
| Student Two | | | Not marked |
|
||||
|
||||
Scenario: Bulk set workflow state as allocated markers
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I set the field "selectall" to "1"
|
||||
And I click on "Change marking state" "button" in the "sticky-footer" "region"
|
||||
And I click on "Change marking state" "button" in the ".modal-footer" "css_element"
|
||||
And I select "Mark" from the "Workflow context" singleselect
|
||||
When I select "Marking completed" from the "Marking workflow state" singleselect
|
||||
And I press "Save changes"
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | Marking completed | Not marked | In marking |
|
||||
| Student Two | Marking completed | Not marked | In marking |
|
||||
# Log in as the other teacher and change their workflow status too.
|
||||
And I am on the "A1" "assign activity" page logged in as teacher2
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I set the field "selectall" to "1"
|
||||
And I click on "Change marking state" "button" in the "sticky-footer" "region"
|
||||
And I click on "Change marking state" "button" in the ".modal-footer" "css_element"
|
||||
And I select "Mark" from the "Workflow context" singleselect
|
||||
And I select "Marking completed" from the "Marking workflow state" singleselect
|
||||
And I press "Save changes"
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | Marking completed | Marking completed | Marking completed |
|
||||
| Student Two | Marking completed | Marking completed | Marking completed |
|
||||
|
||||
Scenario: Grades are only calculated after all marks are given
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I go to "Student One" "Assignment 1" activity advanced marking page
|
||||
When I set the field "Mark out of 100" to "42"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | Marking completed | Not marked | In marking |
|
||||
And the following should not exist in the "submissions" table:
|
||||
| Grade |
|
||||
| 42 |
|
||||
# Now, provide marks as teacher2.
|
||||
And I am on the "A1" "assign activity" page logged in as teacher2
|
||||
And I go to "Student One" "Assignment 1" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "55"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status | Grade |
|
||||
| Student One | Marking completed | Marking completed | Marking completed | 55 |
|
||||
|
||||
Scenario: Grade updates when new allocated marker provides mark
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I change window size to "large"
|
||||
And I go to "Student One" "Assignment 1" activity advanced marking page
|
||||
When I set the field "Mark out of 100" to "50"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
# Now, provide marks as teacher2.
|
||||
And I am on the "A1" "assign activity" page logged in as teacher2
|
||||
And I go to "Student One" "Assignment 1" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "51"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status | Grade |
|
||||
| Student One | Marking completed | Marking completed | Marking completed | 51 |
|
||||
# Swap teacher2 for teacher3 and provide a new mark.
|
||||
And I am on the "A1" "assign activity" page logged in as teacher3
|
||||
And I go to "Student One" "Assignment 1" activity advanced grading page
|
||||
And I set the field "Marking workflow state" to "In marking"
|
||||
And I set the field "Marker 2" to "Teacher Three"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I go to "Student One" "Assignment 1" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "52"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status | Grade |
|
||||
| Student One | Marking completed | Marking completed | Marking completed | 52 |
|
||||
@@ -86,4 +86,31 @@ class behat_mod_assign extends behat_base {
|
||||
$rule = ['contextid' => 1, 'overdueby' => DAYSECS, 'penalty' => 10, 'sortorder' => 0];
|
||||
$DB->insert_record('gradepenalty_duedate_rule', (object) $rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes to the student's advanced marking page.
|
||||
*
|
||||
* @Given /^I go to "(?P<user_fullname>(?:[^"]|\\")*)" "(?P<activity_name>(?:[^"]|\\")*)" activity advanced marking page$/
|
||||
* @param string $userfullname The user's full name including firstname and lastname.
|
||||
* @param string $activityname The activity name
|
||||
*/
|
||||
public function i_go_to_activity_advanced_marking_page(string $userfullname, string $activityname): void {
|
||||
|
||||
// Step to access the user grade page from the grading page.
|
||||
$this->execute('behat_navigation::go_to_breadcrumb_location', $this->escape($activityname));
|
||||
|
||||
$this->execute('behat_general::click_link', get_string('gradeitem:submissions', 'mod_assign'));
|
||||
|
||||
$this->execute(
|
||||
'behat_general::i_click_on_in_the',
|
||||
[
|
||||
$this->escape(get_string('markactions', 'assign')),
|
||||
'actionmenu',
|
||||
$this->escape($userfullname),
|
||||
'table_row',
|
||||
]
|
||||
);
|
||||
|
||||
$this->execute('behat_action_menu::i_choose_in_the_open_action_menu', get_string('markverb', 'mod_assign'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ Feature: In an assignment, teachers can filter displayed submissions by assigned
|
||||
And I press "Save and display"
|
||||
And I am on the "Test assignment name" "assign activity" page
|
||||
And I go to "Student 1" "Test assignment name" activity advanced grading page
|
||||
And I set the field "allocatedmarker" to "Marker 1"
|
||||
And I set the field "Marker 1" to "Marker 1"
|
||||
And I set the field "Notify student" to "0"
|
||||
And I press "Save changes"
|
||||
And I am on the "Test assignment name" Activity page
|
||||
@@ -130,7 +130,7 @@ Feature: In an assignment, teachers can filter displayed submissions by assigned
|
||||
And I change window size to "large"
|
||||
And I go to "Student 1" "Test assignment name" activity advanced grading page
|
||||
# Allocate Marker 1 as the marker of Student 1.
|
||||
And I set the field "allocatedmarker" to "Marker 1"
|
||||
And I set the field "Marker 1" to "Marker 1"
|
||||
And I set the field "Notify student" to "0"
|
||||
And I press "Save changes"
|
||||
And I follow "View all submissions"
|
||||
|
||||
@@ -32,6 +32,7 @@ Feature: In an assignment, teachers can change filters in the grading app
|
||||
| assignsubmission_file_enabled | 0 |
|
||||
| markingworkflow | 1 |
|
||||
| markingallocation | 1 |
|
||||
| markercount | 1 |
|
||||
|
||||
@javascript
|
||||
Scenario: Set filters in the grading table and see them in the grading app
|
||||
@@ -40,7 +41,7 @@ Feature: In an assignment, teachers can change filters in the grading app
|
||||
And I should not see "Course 1 &"
|
||||
And the "title" attribute of "a[title='Course: Course 1 &']" "css_element" should not contain "&"
|
||||
And I should not see "Test assignment name &"
|
||||
And I set the field "allocatedmarker" to "Marker 1"
|
||||
And I set the field "Marker 1" to "Marker 1"
|
||||
And I set the field "workflowstate" to "In marking"
|
||||
And I set the field "Notify student" to "0"
|
||||
And I press "Save changes"
|
||||
@@ -64,7 +65,7 @@ Feature: In an assignment, teachers can change filters in the grading app
|
||||
Scenario: Set filters in the grading app and see them in the grading table
|
||||
Given I am on the "Test assignment name &" Activity page logged in as teacher1
|
||||
And I go to "Student 1" "Test assignment name &" activity advanced grading page
|
||||
And I set the field "allocatedmarker" to "Marker 1"
|
||||
And I set the field "Marker 1" to "Marker 1"
|
||||
And I set the field "workflowstate" to "In marking"
|
||||
And I set the field "Notify student" to "0"
|
||||
And I press "Save changes"
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
@mod @mod_assign @javascript
|
||||
Feature: Complete multi-marking workflow
|
||||
In order to conduct an assignment with multi-marking
|
||||
As a teacher
|
||||
I need to:
|
||||
- Allocate markers to the student(s)
|
||||
- Allocate marks to the student(s) submission(s)
|
||||
- Calculate a final grade based on the configured multi-marking agreement method
|
||||
- Release the final grade to the student(s)
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | One | student1@example.com |
|
||||
| student2 | Student | Two | student2@example.com |
|
||||
| student3 | Student | Three | student3@example.com |
|
||||
| student4 | Student | Four | student4@example.com |
|
||||
| teacher1 | Teacher | One | teacher1@example.com |
|
||||
| teacher2 | Teacher | Two | teacher2@example.com |
|
||||
| teacher3 | Teacher | Three | teacher3@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
| student3 | C1 | student |
|
||||
| student4 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher2 | C1 | editingteacher |
|
||||
| teacher3 | C1 | editingteacher |
|
||||
And the following "groups" exist:
|
||||
| name | course | idnumber | participation |
|
||||
| Group 1 | C1 | G1 | 1 |
|
||||
| Group 2 | C1 | G2 | 1 |
|
||||
And the following "group members" exist:
|
||||
| user | group |
|
||||
| student1 | G1 |
|
||||
| student2 | G1 |
|
||||
| student3 | G2 |
|
||||
| student4 | G2 |
|
||||
And the following "activity" exists:
|
||||
| activity | assign |
|
||||
| course | C1 |
|
||||
| idnumber | A1 |
|
||||
| name | Assignment 1 |
|
||||
| section | 1 |
|
||||
| completion | 1 |
|
||||
| markingworkflow | 1 |
|
||||
| markingallocation | 1 |
|
||||
| markercount | 2 |
|
||||
| multimarkmethod | average |
|
||||
| multimarkrounding | 1 |
|
||||
| grade[modgrade_type] | point |
|
||||
| grade[modgrade_point] | 100 |
|
||||
And the following "activity" exists:
|
||||
| activity | assign |
|
||||
| course | C1 |
|
||||
| idnumber | A2 |
|
||||
| name | Assignment 2 |
|
||||
| section | 1 |
|
||||
| completion | 1 |
|
||||
| markingworkflow | 1 |
|
||||
| markingallocation | 1 |
|
||||
| markercount | 2 |
|
||||
| multimarkmethod | maximum |
|
||||
| multimarkrounding | 1 |
|
||||
| teamsubmission | 1 |
|
||||
| grade[modgrade_type] | point |
|
||||
| grade[modgrade_point] | 100 |
|
||||
|
||||
Scenario: Complete workflow of multi-marking
|
||||
# Firstly, allocate the markers to the students.
|
||||
Given I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I change window size to "large"
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I set the field "selectall" to "1"
|
||||
When I click on "Allocate marker" "button" in the "sticky-footer" "region"
|
||||
And I click on "Allocate marker" "button" in the ".modal-footer" "css_element"
|
||||
And I select "Teacher One" from the "Allocated marker 1" singleselect
|
||||
And I select "Teacher Two" from the "Allocated marker 2" singleselect
|
||||
And I press "Save changes"
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | Teacher One | Teacher Two | Not marked |
|
||||
| Student Two | Teacher One | Teacher Two | Not marked |
|
||||
| Student Three | | | Not marked |
|
||||
| Student Four | | | Not marked |
|
||||
# Then allocate marks to the student submissions as teacher1.
|
||||
And I go to "Student One" "Assignment 1" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "99"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I go to "Student Two" "Assignment 1" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "11"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | 99 | | In marking |
|
||||
| Student Two | 11 | | In marking |
|
||||
| Student Three | | | Not marked |
|
||||
| Student Four | | | Not marked |
|
||||
# Then allocate marks to the student submissions as teacher2.
|
||||
And I am on the "A1" "assign activity" page logged in as teacher2
|
||||
And I go to "Student One" "Assignment 1" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "88"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I go to "Student Two" "Assignment 1" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "22"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A1" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status | Grade |
|
||||
| Student One | 99 | 88 | Marking completed | 94 |
|
||||
| Student Two | 11 | 22 | Marking completed | 17 |
|
||||
| Student Three | | | Not marked | |
|
||||
| Student Four | | | Not marked | |
|
||||
# Then we check the calculated final grade and release them to the students.
|
||||
And I am on the "A1" "assign activity" page logged in as teacher1
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I set the field "selectall" to "1"
|
||||
And I click on "Change marking state" "button" in the "sticky-footer" "region"
|
||||
And I click on "Change marking state" "button" in the "Set marking workflow state" "dialogue"
|
||||
And I set the field "Workflow context" to "Grade"
|
||||
And I set the field "Marking workflow state" to "Released"
|
||||
And I press "Save changes"
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status | Final grade |
|
||||
| Student One | 99 | 88 | Released | 94 |
|
||||
| Student Two | 11 | 22 | Released | 17 |
|
||||
| Student Three | | | Released | |
|
||||
| Student Four | | | Released | |
|
||||
|
||||
Scenario: Complete workflow of multi-marking with group submissions
|
||||
# Firstly, allocate the markers to the students (to test thoroughly, one group will have the same marker
|
||||
# for both students. The other group won't).
|
||||
Given I am on the "A2" "assign activity" page logged in as teacher1
|
||||
And I change window size to "large"
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I click on "Quick grading" "checkbox"
|
||||
When I set the field "Allocated marker 1" in the "Student One" "table_row" to "Teacher One"
|
||||
And I set the field "Allocated marker 1" in the "Student Two" "table_row" to "Teacher One"
|
||||
And I set the field "Allocated marker 1" in the "Student Three" "table_row" to "Teacher One"
|
||||
And I set the field "Allocated marker 1" in the "Student Four" "table_row" to "Teacher One"
|
||||
And I set the field "Allocated marker 2" in the "Student One" "table_row" to "Teacher Two"
|
||||
And I set the field "Allocated marker 2" in the "Student Two" "table_row" to "Teacher Two"
|
||||
And I set the field "Allocated marker 2" in the "Student Three" "table_row" to "Teacher Two"
|
||||
And I set the field "Allocated marker 2" in the "Student Four" "table_row" to "Teacher Three"
|
||||
And I click on "Save" "button" in the "sticky-footer" "region"
|
||||
And I press "Continue"
|
||||
And I click on "Quick grading" "checkbox"
|
||||
Then the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | Teacher One | Teacher Two | |
|
||||
| Student Two | Teacher One | Teacher Two | |
|
||||
| Student Three | Teacher One | Teacher Two | |
|
||||
| Student Four | Teacher One | Teacher Three | |
|
||||
# Next we test adding a mark as Marker 1 (teacher1) to a student in Group 1. This should populate to the other
|
||||
# student in Group 1, but not the student with this same marker, who is not in Group 1.
|
||||
And I go to "Student One" "Assignment 2" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "50"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I go to "Student Four" "Assignment 2" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "60"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A2" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | 50 | | In marking |
|
||||
| Student Two | 50 | | In marking |
|
||||
| Student Three | 60 | | In marking |
|
||||
| Student Four | 60 | | In marking |
|
||||
# Next we add a mark as teacher2 to a student in both groups. The mark given to the student in Group 1 should
|
||||
# populate to the other student in Group 1. The mark given to the student in Group 2 should not, as they are not
|
||||
# an allocated marker for that final student.
|
||||
And I am on the "A2" "assign activity" page logged in as teacher2
|
||||
And I go to "Student One" "Assignment 2" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "30"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A2" "assign activity" page
|
||||
And I go to "Student Three" "Assignment 2" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "15"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A2" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | 50 | 30 | Marking completed |
|
||||
| Student Two | 50 | 30 | Marking completed |
|
||||
| Student Three | 60 | 15 | Marking completed |
|
||||
| Student Four | 60 | | In marking |
|
||||
# Then we login as teacher3 and add that last mark to the final student.
|
||||
And I am on the "A2" "assign activity" page logged in as teacher3
|
||||
And I go to "Student Four" "Assignment 2" activity advanced marking page
|
||||
And I set the field "Mark out of 100" to "99"
|
||||
And I set the field "Marking workflow state" to "Marking completed"
|
||||
And I press "Save changes"
|
||||
And I am on the "A2" "assign activity" page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status |
|
||||
| Student One | 50 | 30 | Marking completed |
|
||||
| Student Two | 50 | 30 | Marking completed |
|
||||
| Student Three | 60 | 15 | Marking completed |
|
||||
| Student Four | 60 | 99 | Marking completed |
|
||||
# Then we check that the grades have been calculated correctly.
|
||||
And I am on the "A2" "assign activity" page logged in as teacher1
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status | Grade |
|
||||
| Student One | 50 | 30 | Marking completed | 50 |
|
||||
| Student Two | 50 | 30 | Marking completed | 50 |
|
||||
| Student Three | 60 | 15 | Marking completed | 60 |
|
||||
| Student Four | 60 | 99 | Marking completed | 99 |
|
||||
# Then we release them all and check the final grade column.
|
||||
And I am on the "A2" "assign activity" page logged in as teacher1
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I set the field "selectall" to "1"
|
||||
And I click on "Change marking state" "button" in the "sticky-footer" "region"
|
||||
And I click on "Change marking state" "button" in the "Set marking workflow state" "dialogue"
|
||||
And I set the field "Workflow context" to "Grade"
|
||||
And I set the field "Marking workflow state" to "Released"
|
||||
And I press "Save changes"
|
||||
And the following should exist in the "submissions" table:
|
||||
| First name | Marker 1 | Marker 2 | Status | Final grade |
|
||||
| Student One | 50 | 30 | Released | 50 |
|
||||
| Student Two | 50 | 30 | Released | 50 |
|
||||
| Student Three | 60 | 15 | Released | 60 |
|
||||
| Student Four | 60 | 100 | Released | 99 |
|
||||
@@ -1076,7 +1076,8 @@ final class events_test extends \advanced_testcase {
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
$assign = $this->create_instance($course);
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
$assign = $this->create_instance($course, ['markercount' => 1]);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
@@ -735,7 +735,6 @@ final class externallib_test extends \mod_assign\externallib_advanced_testcase {
|
||||
$userflag->mailed = 0;
|
||||
$userflag->extensionduedate = 0;
|
||||
$userflag->workflowstate = 'inmarking';
|
||||
$userflag->allocatedmarker = $USER->id;
|
||||
|
||||
$DB->insert_record('assign_user_flags', $userflag);
|
||||
|
||||
@@ -757,7 +756,6 @@ final class externallib_test extends \mod_assign\externallib_advanced_testcase {
|
||||
$this->assertEquals(0, $userflag['mailed']);
|
||||
$this->assertEquals(0, $userflag['extensionduedate']);
|
||||
$this->assertEquals('inmarking', $userflag['workflowstate']);
|
||||
$this->assertEquals($USER->id, $userflag['allocatedmarker']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1742,7 +1740,6 @@ final class externallib_test extends \mod_assign\externallib_advanced_testcase {
|
||||
$userflags = array();
|
||||
$userflag['userid'] = $student->id;
|
||||
$userflag['workflowstate'] = 'inmarking';
|
||||
$userflag['allocatedmarker'] = $USER->id;
|
||||
$userflags = array($userflag);
|
||||
|
||||
$createduserflags = mod_assign_external::set_user_flags($assign->id, $userflags);
|
||||
@@ -1759,7 +1756,6 @@ final class externallib_test extends \mod_assign\externallib_advanced_testcase {
|
||||
$this->assertEquals(2, $createduserflag->mailed);
|
||||
$this->assertEquals(0, $createduserflag->extensionduedate);
|
||||
$this->assertEquals('inmarking', $createduserflag->workflowstate);
|
||||
$this->assertEquals($USER->id, $createduserflag->allocatedmarker);
|
||||
|
||||
// Create update data.
|
||||
$userflags = array();
|
||||
@@ -1781,7 +1777,6 @@ final class externallib_test extends \mod_assign\externallib_advanced_testcase {
|
||||
$this->assertEquals(2, $updateduserflag->mailed);
|
||||
$this->assertEquals(0, $updateduserflag->extensionduedate);
|
||||
$this->assertEquals('readyforreview', $updateduserflag->workflowstate);
|
||||
$this->assertEquals($USER->id, $updateduserflag->allocatedmarker);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2824,7 +2819,7 @@ final class externallib_test extends \mod_assign\externallib_advanced_testcase {
|
||||
$DB->update_record('user', $student);
|
||||
|
||||
$this->setUser($teacher);
|
||||
$participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, true, true);
|
||||
$participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, true, true, false);
|
||||
$participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
|
||||
$this->assertCount(1, $participants);
|
||||
|
||||
@@ -2843,7 +2838,7 @@ final class externallib_test extends \mod_assign\externallib_advanced_testcase {
|
||||
$this->assertEquals('', $participant['submissionstatus']);
|
||||
$this->assertArrayHasKey('enrolledcourses', $participant);
|
||||
|
||||
$participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, false, true);
|
||||
$participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, false, true, false);
|
||||
$participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
|
||||
// Check that the list of courses the participant is enrolled is not returned.
|
||||
$participant = $participants[0];
|
||||
@@ -2919,7 +2914,7 @@ final class externallib_test extends \mod_assign\externallib_advanced_testcase {
|
||||
$this->setUser($teacher);
|
||||
|
||||
// Test mod_assign_external::list_participants.
|
||||
$participants = mod_assign_external::list_participants($assignmodule->id, $group->id, '', 0, 0, false, true, true);
|
||||
$participants = mod_assign_external::list_participants($assignmodule->id, $group->id, '', 0, 0, false, true, true, false);
|
||||
$participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
|
||||
$this->assertEquals($group->id, $participants[0]['groupid']);
|
||||
$this->assertEquals(format_string($gname, true), $participants[0]['groupname']);
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ class mod_assign_testable_assign extends assign {
|
||||
$data = array();
|
||||
$data['id'] = $this->get_course_module()->id;
|
||||
$data['selectedusers'] = $selectedusers;
|
||||
$data['allocatedmarker'] = $markerid;
|
||||
$data['allocatedmarker1'] = $markerid;
|
||||
$data['action'] = 'setbatchmarkingallocation';
|
||||
mod_assign_batch_set_allocatedmarker_form::mock_submit($data);
|
||||
|
||||
|
||||
@@ -43,6 +43,12 @@ class behat_mod_assign_generator extends behat_generator_base {
|
||||
'required' => ['assign', 'user', 'extensionduedate'],
|
||||
'switchids' => ['assign' => 'cmid', 'user' => 'userid'],
|
||||
],
|
||||
'marker_allocations' => [
|
||||
'singular' => 'marker_allocation',
|
||||
'datagenerator' => 'marker_allocation',
|
||||
'required' => ['assign', 'user', 'marker'],
|
||||
'switchids' => ['assign' => 'cmid', 'user' => 'userid', 'marker' => 'markerid'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -55,4 +61,15 @@ class behat_mod_assign_generator extends behat_generator_base {
|
||||
protected function get_assign_id(string $identifier): int {
|
||||
return $this->get_cm_by_activity_name('assign', $identifier)->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user id for the marker based on their username.
|
||||
*
|
||||
* @param string $username The user's username
|
||||
* @return int The user's ID
|
||||
*/
|
||||
protected function get_marker_id(string $username): int {
|
||||
return $this->get_user_id($username);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,6 +59,9 @@ class mod_assign_generator extends testing_module_generator {
|
||||
'maxattempts' => 1,
|
||||
'markingworkflow' => 0,
|
||||
'markingallocation' => 0,
|
||||
'markercount' => 0,
|
||||
'multimarkmethod' => null,
|
||||
'multimarkrounding' => null,
|
||||
'markinganonymous' => 0,
|
||||
'activityformat' => 0,
|
||||
'timelimit' => 0,
|
||||
@@ -203,4 +206,36 @@ class mod_assign_generator extends testing_module_generator {
|
||||
|
||||
$DB->insert_record('assign_overrides', (object) $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a marker allocation record.
|
||||
*
|
||||
* @param array $data Array containing: ['assignid', 'userid', 'markerid']
|
||||
*/
|
||||
public function create_marker_allocation(array $data): void {
|
||||
global $DB;
|
||||
|
||||
if (!isset($data['cmid'])) {
|
||||
throw new coding_exception('Must specify assign when creating a marker allocation.');
|
||||
}
|
||||
|
||||
if (!isset($data['userid'])) {
|
||||
throw new coding_exception('Must specify user when creating a marker allocation.');
|
||||
}
|
||||
|
||||
if (!isset($data['markerid'])) {
|
||||
throw new coding_exception('Must specify marker when creating a marker allocation.');
|
||||
}
|
||||
|
||||
[$course, $cm] = get_course_and_cm_from_cmid($data['cmid'], 'assign');
|
||||
$context = context_module::instance($cm->id);
|
||||
$assign = new assign($context, $cm, $course);
|
||||
|
||||
$DB->insert_record('assign_allocated_marker', [
|
||||
'student' => $data['userid'],
|
||||
'assignment' => $assign->get_instance()->id,
|
||||
'marker' => $data['markerid'],
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3349,46 +3349,6 @@ You can see it appended to your <a href="' . $assignurl .
|
||||
$this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a student allocated a specific marker is only shown to that marker.
|
||||
*/
|
||||
public function test_markerallocation(): void {
|
||||
global $PAGE;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
$otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
|
||||
$assign = $this->create_instance($course, [
|
||||
'markingworkflow' => 1,
|
||||
'markingallocation' => 1,
|
||||
]);
|
||||
|
||||
$PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
|
||||
|
||||
// Allocate marker to submission.
|
||||
$this->mark_submission($teacher, $assign, $student, null, [
|
||||
'allocatedmarker' => $teacher->id,
|
||||
]);
|
||||
|
||||
// Check the allocated marker can view the submission.
|
||||
$this->setUser($teacher);
|
||||
$users = $assign->list_participants(0, true);
|
||||
$this->assertEquals(1, count($users));
|
||||
$this->assertTrue(isset($users[$student->id]));
|
||||
|
||||
$cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
|
||||
$context = \context_module::instance($cm->id);
|
||||
$assign = new mod_assign_testable_assign($context, $cm, $course);
|
||||
|
||||
// Check that other teachers can't view this submission.
|
||||
$this->setUser($otherteacher);
|
||||
$users = $assign->list_participants(0, true);
|
||||
$this->assertEquals(0, count($users));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that a teacher cannot submit for students as standard.
|
||||
*/
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
namespace mod_assign;
|
||||
|
||||
use assign;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/lib/accesslib.php');
|
||||
require_once($CFG->dirroot . '/course/lib.php');
|
||||
require_once($CFG->dirroot . '/mod/assign/locallib.php');
|
||||
|
||||
/**
|
||||
* Unit tests for (some of) mod/assign/markerallocaion_test.php.
|
||||
@@ -29,6 +32,7 @@ require_once($CFG->dirroot . '/course/lib.php');
|
||||
* @category test
|
||||
* @copyright 2017 Andrés Melo <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \assign
|
||||
*/
|
||||
final class markerallocation_test extends \advanced_testcase {
|
||||
|
||||
@@ -36,20 +40,24 @@ final class markerallocation_test extends \advanced_testcase {
|
||||
private $course;
|
||||
|
||||
/**
|
||||
* Create all the needed elements to test the difference between both functions.
|
||||
* @var array Generated users
|
||||
*/
|
||||
public function test_markerusers(): void {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
private array $users = [];
|
||||
|
||||
// Create a course, by default it is created with 5 sections.
|
||||
$this->course = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Setting assing module, markingworkflow and markingallocation set to 1 to enable marker allocation.
|
||||
$record = new \stdClass();
|
||||
$record->course = $this->course;
|
||||
/**
|
||||
* @var array Generated groups
|
||||
*/
|
||||
private array $groups = [];
|
||||
|
||||
/**
|
||||
* Create the assignment object for testing.
|
||||
*
|
||||
* @param array $args Array of options that can be overwritten.
|
||||
* @return assign
|
||||
*/
|
||||
private function create_assignment(array $args = []): assign {
|
||||
$modulesettings = [
|
||||
'course' => $this->course->id,
|
||||
'alwaysshowdescription' => 1,
|
||||
'submissiondrafts' => 1,
|
||||
'requiresubmissionstatement' => 0,
|
||||
@@ -58,63 +66,206 @@ final class markerallocation_test extends \advanced_testcase {
|
||||
'sendlatenotifications' => 0,
|
||||
'duedate' => 0,
|
||||
'allowsubmissionsfromdate' => 0,
|
||||
'grade' => 100,
|
||||
'grade' => (!isset($args['scale'])) ? 100 : null,
|
||||
'cutoffdate' => 0,
|
||||
'teamsubmission' => 0,
|
||||
'teamsubmission' => ($args['teamsubmission']) ?? 0,
|
||||
'requireallteammemberssubmit' => 0,
|
||||
'teamsubmissiongroupingid' => 0,
|
||||
'blindmarking' => 0,
|
||||
'attemptreopenmethod' => 'untilpass',
|
||||
'maxattempts' => 1,
|
||||
'markingworkflow' => 1,
|
||||
'markingallocation' => 1,
|
||||
'markercount' => ($args['markercount']) ?? ASSIGN_MULTIMARKING_DEFAULT_MARKERS,
|
||||
'multimarkmethod' => ($args['multimarkmethod']) ?? ASSIGN_MULTIMARKING_METHOD_MANUAL,
|
||||
'multimarkrounding' => ($args['multimarkrounding']) ?? null,
|
||||
];
|
||||
|
||||
$assignelement = $this->getDataGenerator()->create_module('assign', $record, $modulesettings);
|
||||
if (isset($args['scale'])) {
|
||||
$scale = $this->getDataGenerator()->create_scale();
|
||||
$modulesettings['gradetype'] = GRADE_TYPE_SCALE;
|
||||
$modulesettings['gradescale'] = $scale->id;
|
||||
}
|
||||
|
||||
$coursesectionid = course_add_cm_to_section($this->course->id, $assignelement->id, 1, null, 'assign');
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
|
||||
$instance = $generator->create_instance($modulesettings);
|
||||
[$course, $cm] = get_course_and_cm_from_instance($instance->id, 'assign');
|
||||
$context = \core\context\module::instance($cm->id);
|
||||
$assignment = new assign($context, $cm, $course);
|
||||
return $assignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup all required test data.
|
||||
*/
|
||||
private function setup_data(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a course, by default it is created with 5 sections.
|
||||
$this->course = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Adding users to the course.
|
||||
$userdata = array();
|
||||
$userdata['firstname'] = 'teacher1';
|
||||
$userdata['lasttname'] = 'lastname_teacher1';
|
||||
|
||||
$user1 = $this->getDataGenerator()->create_user($userdata);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($user1->id, $this->course->id, 'teacher');
|
||||
$this->users[0] = $this->getDataGenerator()->create_user($userdata);
|
||||
$this->getDataGenerator()->enrol_user($this->users[0]->id, $this->course->id, 'editingteacher');
|
||||
|
||||
$userdata = array();
|
||||
$userdata['firstname'] = 'teacher2';
|
||||
$userdata['lasttname'] = 'lastname_teacher2';
|
||||
|
||||
$user2 = $this->getDataGenerator()->create_user($userdata);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($user2->id, $this->course->id, 'teacher');
|
||||
$this->users[1] = $this->getDataGenerator()->create_user($userdata);
|
||||
$this->getDataGenerator()->enrol_user($this->users[1]->id, $this->course->id, 'editingteacher');
|
||||
|
||||
$userdata = array();
|
||||
$userdata['firstname'] = 'student';
|
||||
$userdata['lasttname'] = 'lastname_student';
|
||||
|
||||
$user3 = $this->getDataGenerator()->create_user($userdata);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($user3->id, $this->course->id, 'student');
|
||||
$this->users[2] = $this->getDataGenerator()->create_user($userdata);
|
||||
$this->getDataGenerator()->enrol_user($this->users[2]->id, $this->course->id, 'student');
|
||||
|
||||
// Adding manager to the system.
|
||||
$userdata = array();
|
||||
$userdata['firstname'] = 'Manager';
|
||||
$userdata['lasttname'] = 'lastname_Manager';
|
||||
|
||||
$user4 = $this->getDataGenerator()->create_user($userdata);
|
||||
|
||||
// Getting id of manager role.
|
||||
$this->users[3] = $this->getDataGenerator()->create_user($userdata);
|
||||
$managerrole = $DB->get_record('role', array('shortname' => 'manager'));
|
||||
if (!empty($managerrole)) {
|
||||
// By default the context of the system is assigned.
|
||||
$idassignment = $this->getDataGenerator()->role_assign($managerrole->id, $user4->id);
|
||||
$this->getDataGenerator()->role_assign($managerrole->id, $this->users[3]->id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup group data for teamsubmission tests.
|
||||
*/
|
||||
private function setup_group_data(): void {
|
||||
$this->resetAfterTest(false);
|
||||
|
||||
// Create a course, by default it is created with 5 sections.
|
||||
$this->course = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Split users into seaprate arrays for easier use here.
|
||||
$teachers = [];
|
||||
$students = [];
|
||||
|
||||
// Adding teachers to the course.
|
||||
for ($i = 1; $i <= 2; $i++) {
|
||||
$userdata = [];
|
||||
$userdata['firstname'] = 'teacher' . $i;
|
||||
$userdata['lasttname'] = 'lastname_teacher' . $i;
|
||||
$teachers[$i] = $this->getDataGenerator()->create_user($userdata);
|
||||
$this->getDataGenerator()->enrol_user($teachers[$i]->id, $this->course->id, 'teacher');
|
||||
}
|
||||
|
||||
$oldusers = array($user1, $user2, $user4);
|
||||
$newusers = array($user1, $user2);
|
||||
// Adding students to the course.
|
||||
for ($i = 1; $i <= 6; $i++) {
|
||||
$userdata = [];
|
||||
$userdata['firstname'] = 'student' . $i;
|
||||
$userdata['lasttname'] = 'lastname_student' . $i;
|
||||
$students[$i] = $this->getDataGenerator()->create_user($userdata);
|
||||
$this->getDataGenerator()->enrol_user($students[$i]->id, $this->course->id, 'student');
|
||||
}
|
||||
|
||||
// Adding students to groups.
|
||||
$this->groups['A'] = $this->getDataGenerator()->create_group(['courseid' => $this->course->id, 'name' => 'A']);
|
||||
$this->groups['B'] = $this->getDataGenerator()->create_group(['courseid' => $this->course->id, 'name' => 'B']);
|
||||
foreach ($students as $studentnumber => $user) {
|
||||
if ($studentnumber <= 3) {
|
||||
groups_add_member($this->groups['A'], $user);
|
||||
} else {
|
||||
groups_add_member($this->groups['B'], $user);
|
||||
}
|
||||
}
|
||||
|
||||
$this->users = ['students' => $students, 'teachers' => $teachers];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test marker allocation and marking with group submissions.
|
||||
*
|
||||
* @covers ::update_allocated_markers, ::save_grade
|
||||
*/
|
||||
public function test_allocated_markers_with_group_submissions(): void {
|
||||
$this->setup_group_data();
|
||||
$assignment = $this->create_assignment([
|
||||
'teamsubmission' => 1,
|
||||
]);
|
||||
|
||||
// To test the logic that a marker should not be able to update anyone not in their group
|
||||
// we will use the "public" method `save_grade` instead of the internal `update_mark`.
|
||||
// Firstly, allocate teacher1 to every student in group A.
|
||||
foreach ($this->users['students'] as $studentnumber => $student) {
|
||||
if ($studentnumber <= 3) {
|
||||
$assignment->update_allocated_markers($student->id, [$this->users['teachers'][1]->id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate a mark to the first student in the group.
|
||||
// This should spread out to the other students in the group as well.
|
||||
$this->setUser($this->users['teachers'][1]);
|
||||
|
||||
// Before we save it, we need to create the submission record, which won't happen from just saving it.
|
||||
// We are passing -1 as userid because it's a required argument, but if the groupid is present, then
|
||||
// the `get_group_submission` function ignores it, so it just needs any value really.
|
||||
$assignment->get_group_submission(-1, $this->groups['A']->id, true);
|
||||
|
||||
// Then save it.
|
||||
$assignment->save_grade($this->users['students'][1]->id, (object)[
|
||||
'mark' => 50,
|
||||
'applytoall' => 1,
|
||||
'attemptnumber' => -1,
|
||||
]);
|
||||
|
||||
// All 3 students in the group should now have the same mark from this allocated marker.
|
||||
foreach ($this->users['students'] as $studentnumber => $student) {
|
||||
if ($studentnumber <= 3) {
|
||||
$gradeobject = $assignment->get_user_grade($student->id, true);
|
||||
$mark = $assignment->get_mark($gradeobject->id, $this->users['teachers'][1]->id);
|
||||
$this->assertEquals(50, $mark->mark);
|
||||
}
|
||||
}
|
||||
|
||||
// Now allocate teacher2 to 2 out of 3 students in group B.
|
||||
foreach ($this->users['students'] as $studentnumber => $student) {
|
||||
if ($studentnumber > 3 && $studentnumber < 6) {
|
||||
$assignment->update_allocated_markers($student->id, [$this->users['teachers'][2]->id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate a mark to the first student in the group.
|
||||
$this->setUser($this->users['teachers'][2]);
|
||||
$assignment->get_group_submission(-1, $this->groups['B']->id, true);
|
||||
$assignment->save_grade($this->users['students'][4]->id, (object)[
|
||||
'mark' => 99,
|
||||
'applytoall' => 1,
|
||||
'attemptnumber' => -1,
|
||||
]);
|
||||
|
||||
// Only 2 out of 3 students should have the grade applied.
|
||||
foreach ($this->users['students'] as $studentnumber => $student) {
|
||||
if ($studentnumber > 3) {
|
||||
$gradeobject = $assignment->get_user_grade($student->id, true);
|
||||
$mark = $assignment->get_mark($gradeobject->id, $this->users['teachers'][2]->id);
|
||||
if ($studentnumber < 6) {
|
||||
$this->assertEquals(99, $mark->mark);
|
||||
} else {
|
||||
$this->assertFalse($mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all the needed elements to test the difference between both functions.
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
public function test_markerusers(): void {
|
||||
$this->setup_data();
|
||||
|
||||
$oldusers = [$this->users[0], $this->users[1], $this->users[3]];
|
||||
$newusers = [$this->users[0], $this->users[1]];
|
||||
|
||||
list($sort, $params) = users_order_by_sql('u');
|
||||
|
||||
@@ -140,6 +291,329 @@ final class markerallocation_test extends \advanced_testcase {
|
||||
}
|
||||
|
||||
$this->assertEquals(count($oldmarkers), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test functionality around having multiple allocated markers.
|
||||
*
|
||||
* @covers ::update_allocated_markers, ::update_mark
|
||||
*/
|
||||
public function test_multiple_marker_allocation(): void {
|
||||
|
||||
$this->setup_data();
|
||||
$assignment = $this->create_assignment();
|
||||
|
||||
// To start with, confirm that no markers are allocated to the student submission.
|
||||
$markers = $assignment->get_allocated_markers($this->users[2]->id);
|
||||
$this->assertCount(0, $markers);
|
||||
|
||||
// Allocate both teachers to the student assignment.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [
|
||||
$this->users[0]->id,
|
||||
$this->users[1]->id,
|
||||
]);
|
||||
$markers = $assignment->get_allocated_markers($this->users[2]->id);
|
||||
$this->assertCount(2, $markers);
|
||||
|
||||
// Now test that we can add a mark to the submission.
|
||||
// Firstly, there should be no mark currently for either marker.
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, true);
|
||||
$mark = $assignment->get_mark($gradeobject->id, $this->users[0]->id);
|
||||
$this->assertFalse($mark);
|
||||
|
||||
// Assign a mark as teacher1.
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, 99);
|
||||
|
||||
// Now check that we can find the mark.
|
||||
$mark = $assignment->get_mark($gradeobject->id, $this->users[0]->id);
|
||||
$this->assertEquals("99.00000", $mark->mark);
|
||||
|
||||
// Assign a mark as teacher2.
|
||||
$gradeobject->grader = $this->users[1]->id;
|
||||
$assignment->update_mark($gradeobject, 11);
|
||||
|
||||
// Now check that we can find the mark.
|
||||
$mark = $assignment->get_mark($gradeobject->id, $this->users[1]->id);
|
||||
$this->assertEquals("11.00000", $mark->mark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test manual calculation of final grade.
|
||||
*
|
||||
* @covers ::update_mark
|
||||
*/
|
||||
public function test_calculated_marker_grade_manual(): void {
|
||||
$this->setup_data();
|
||||
$assignment = $this->create_assignment();
|
||||
|
||||
// Allocate both teachers to the student assignment.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [
|
||||
$this->users[0]->id,
|
||||
$this->users[1]->id,
|
||||
]);
|
||||
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, true);
|
||||
|
||||
// Assign a mark as teacher1.
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, 99);
|
||||
|
||||
// Assign a mark as teacher2.
|
||||
$gradeobject->grader = $this->users[1]->id;
|
||||
$assignment->update_mark($gradeobject, 11);
|
||||
|
||||
// With manual calculation, there should be no grade set yet.
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, false);
|
||||
$this->assertEquals(-1, $gradeobject->grade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test "maximum" calculation of final grade when using scale grading.
|
||||
*
|
||||
* @covers ::update_mark
|
||||
*/
|
||||
public function test_calculated_marker_grade_maximum(): void {
|
||||
$this->setup_data();
|
||||
$assignment = $this->create_assignment([
|
||||
'multimarkmethod' => ASSIGN_MULTIMARKING_METHOD_MAX,
|
||||
]);
|
||||
|
||||
// Allocate both teachers to the student assignment.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [
|
||||
$this->users[0]->id,
|
||||
$this->users[1]->id,
|
||||
]);
|
||||
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, true);
|
||||
|
||||
// Assign a mark as teacher1.
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, 11);
|
||||
|
||||
// Assign a mark as teacher2.
|
||||
$gradeobject->grader = $this->users[1]->id;
|
||||
$assignment->update_mark($gradeobject, 99);
|
||||
|
||||
// With max calculation, the grade should be the highest one.
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, false);
|
||||
$this->assertEquals(99, $gradeobject->grade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test "average" calculation of final grade when using rounding of "none".
|
||||
*
|
||||
* @covers ::update_mark
|
||||
*/
|
||||
public function test_calculated_marker_grade_average_round_none(): void {
|
||||
$this->setup_data();
|
||||
$assignment = $this->create_assignment([
|
||||
'multimarkmethod' => ASSIGN_MULTIMARKING_METHOD_AVERAGE,
|
||||
'multimarkrounding' => ASSIGN_MULTIMARKING_AVERAGE_ROUND_NONE,
|
||||
]);
|
||||
|
||||
// Allocate both teachers to the student assignment.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [
|
||||
$this->users[0]->id,
|
||||
$this->users[1]->id,
|
||||
]);
|
||||
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, true);
|
||||
|
||||
// Assign a mark as teacher1.
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, 90);
|
||||
|
||||
// Assign a mark as teacher2.
|
||||
$gradeobject->grader = $this->users[1]->id;
|
||||
$assignment->update_mark($gradeobject, 25);
|
||||
|
||||
// With avg calculation and no rounding, the grade should be 57.5.
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, false);
|
||||
$this->assertEquals(57.5, $gradeobject->grade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test "average" calculation of final grade when using rounding of "down".
|
||||
*
|
||||
* @covers ::update_mark
|
||||
*/
|
||||
public function test_calculated_marker_grade_average_rounding_down(): void {
|
||||
$this->setup_data();
|
||||
$assignment = $this->create_assignment([
|
||||
'multimarkmethod' => ASSIGN_MULTIMARKING_METHOD_AVERAGE,
|
||||
'multimarkrounding' => ASSIGN_MULTIMARKING_AVERAGE_ROUND_DOWN,
|
||||
]);
|
||||
|
||||
// Allocate both teachers to the student assignment.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [
|
||||
$this->users[0]->id,
|
||||
$this->users[1]->id,
|
||||
]);
|
||||
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, true);
|
||||
|
||||
// Assign a mark as teacher1.
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, 90);
|
||||
|
||||
// Assign a mark as teacher2.
|
||||
$gradeobject->grader = $this->users[1]->id;
|
||||
$assignment->update_mark($gradeobject, 25);
|
||||
|
||||
// With avg calculation and down rounding, the grade should be 57.
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, false);
|
||||
$this->assertEquals(57, $gradeobject->grade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the grade calculation from marks using method "average" with up rounding, sets the correct grade.
|
||||
*
|
||||
* @covers ::update_mark
|
||||
*/
|
||||
public function test_calculated_marker_grade_average_round_up(): void {
|
||||
$this->setup_data();
|
||||
$assignment = $this->create_assignment([
|
||||
'multimarkmethod' => ASSIGN_MULTIMARKING_METHOD_AVERAGE,
|
||||
'multimarkrounding' => ASSIGN_MULTIMARKING_AVERAGE_ROUND_UP,
|
||||
]);
|
||||
|
||||
// Allocate both teachers to the student assignment.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [
|
||||
$this->users[0]->id,
|
||||
$this->users[1]->id,
|
||||
]);
|
||||
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, true);
|
||||
|
||||
// Assign a mark as teacher1.
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, 90);
|
||||
|
||||
// Assign a mark as teacher2.
|
||||
$gradeobject->grader = $this->users[1]->id;
|
||||
$assignment->update_mark($gradeobject, 25);
|
||||
|
||||
// With avg calculation and up rounding, the grade should be 58.
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, false);
|
||||
$this->assertEquals(58, $gradeobject->grade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the grade calculation from marks using method "average" with natural rounding, sets the correct grade.
|
||||
*
|
||||
* @covers ::update_mark
|
||||
*/
|
||||
public function test_calculated_marker_grade_average_round_natural(): void {
|
||||
$this->setup_data();
|
||||
$assignment = $this->create_assignment([
|
||||
'multimarkmethod' => ASSIGN_MULTIMARKING_METHOD_AVERAGE,
|
||||
'multimarkrounding' => ASSIGN_MULTIMARKING_AVERAGE_ROUND_NATURAL,
|
||||
]);
|
||||
|
||||
// Allocate both teachers to the student assignment.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [
|
||||
$this->users[0]->id,
|
||||
$this->users[1]->id,
|
||||
]);
|
||||
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, true);
|
||||
|
||||
// Assign a mark as teacher1.
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, 90);
|
||||
|
||||
// Assign a mark as teacher2.
|
||||
$gradeobject->grader = $this->users[1]->id;
|
||||
$assignment->update_mark($gradeobject, 25);
|
||||
|
||||
// With avg calculation and natural rounding, the grade should be 58.
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, false);
|
||||
$this->assertEquals(58, $gradeobject->grade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the workflow state changes on the overall grade based on marker states.
|
||||
*
|
||||
* @covers ::update_mark, ::calculate_and_save_overall_workflow_state
|
||||
*/
|
||||
public function test_calculated_marker_workflow(): void {
|
||||
$this->setup_data();
|
||||
$assignment = $this->create_assignment();
|
||||
|
||||
// Allocate both teachers to the student assignment.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [
|
||||
$this->users[0]->id,
|
||||
$this->users[1]->id,
|
||||
]);
|
||||
|
||||
// First confirm that the overall grade workflow state is not set.
|
||||
$flags = $assignment->get_user_flags($this->users[2]->id, true);
|
||||
$this->assertEmpty($flags->workflowstate);
|
||||
|
||||
// One marker then sets their mark to be in the state "In Marking".
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, true);
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, null, ASSIGN_MARKING_WORKFLOW_STATE_INMARKING);
|
||||
$assignment->calculate_and_save_overall_workflow_state($gradeobject, $flags, $flags->workflowstate);
|
||||
|
||||
// Re-check the overall workflow. This should now be "In Marking" as well.
|
||||
$flags = $assignment->get_user_flags($this->users[2]->id, true);
|
||||
$this->assertEquals(ASSIGN_MARKING_WORKFLOW_STATE_INMARKING, $flags->workflowstate);
|
||||
|
||||
// Now this teacher marks theirs as "Marking Complete".
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, 90, ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW);
|
||||
$assignment->calculate_and_save_overall_workflow_state($gradeobject, $flags, $flags->workflowstate);
|
||||
|
||||
// Nothing should change on the overall state, that should still be In Marking.
|
||||
$flags = $assignment->get_user_flags($this->users[2]->id, true);
|
||||
$this->assertEquals(ASSIGN_MARKING_WORKFLOW_STATE_INMARKING, $flags->workflowstate);
|
||||
|
||||
// Now the second marker sets theirs as "Marking Complete".
|
||||
$gradeobject->grader = $this->users[1]->id;
|
||||
$assignment->update_mark($gradeobject, 70, ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW);
|
||||
$assignment->calculate_and_save_overall_workflow_state($gradeobject, $flags, $flags->workflowstate);
|
||||
|
||||
// Now that both are complete, the overall state should be the same.
|
||||
$flags = $assignment->get_user_flags($this->users[2]->id, true);
|
||||
$this->assertEquals(ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW, $flags->workflowstate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that when we remove a marker their marks are not counted towards anything.
|
||||
*
|
||||
* @covers ::update_mark
|
||||
*/
|
||||
public function test_unallocated_marker_not_included_in_mark_calculations(): void {
|
||||
$this->setup_data();
|
||||
$assignment = $this->create_assignment([
|
||||
'multimarkmethod' => ASSIGN_MULTIMARKING_METHOD_AVERAGE,
|
||||
'multimarkrounding' => ASSIGN_MULTIMARKING_AVERAGE_ROUND_NATURAL,
|
||||
]);
|
||||
|
||||
// Allocate both teachers to the student assignment.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [
|
||||
$this->users[0]->id,
|
||||
$this->users[1]->id,
|
||||
]);
|
||||
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, true);
|
||||
|
||||
// Assign a mark as teacher1.
|
||||
$gradeobject->grader = $this->users[0]->id;
|
||||
$assignment->update_mark($gradeobject, 90);
|
||||
|
||||
// Now we remove teacher1 and add manager instead. So we have manager and teacher2 as the markers.
|
||||
$assignment->update_allocated_markers($this->users[2]->id, [$this->users[3]->id, $this->users[1]->id]);
|
||||
|
||||
// Now add a marker from teacher2.
|
||||
$gradeobject->grader = $this->users[1]->id;
|
||||
$assignment->update_mark($gradeobject, 10);
|
||||
|
||||
// At this point, though we've had 2 marks, only 1 of the allocated markers has marked.
|
||||
// So the grade should not be set.
|
||||
$gradeobject = $assignment->get_user_grade($this->users[2]->id, false);
|
||||
$this->assertEquals(-1, $gradeobject->grade);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,10 @@ final class provider_test extends provider_testcase {
|
||||
'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
|
||||
'maxattempts' => 3,
|
||||
'assignsubmission_onlinetext_enabled' => true,
|
||||
'assignfeedback_comments_enabled' => true
|
||||
'assignfeedback_comments_enabled' => true,
|
||||
'markingworkflow' => 1,
|
||||
'markingallocation' => 1,
|
||||
'markercount' => 2,
|
||||
]);
|
||||
|
||||
$context = $assign->get_context();
|
||||
@@ -253,6 +256,14 @@ final class provider_test extends provider_testcase {
|
||||
// Give the submission a grade.
|
||||
$assign->save_grade($user->id, $data);
|
||||
|
||||
// Allocate the teacher as a marker on the assignment.
|
||||
$assign->update_allocated_markers($user->id, [$teacher->id]);
|
||||
|
||||
// Give the submission a mark.
|
||||
$gradeobject = $assign->get_user_grade($user->id, true);
|
||||
$gradeobject->grader = $teacher->id;
|
||||
$assign->update_mark($gradeobject, 99.9);
|
||||
|
||||
/** @var \core_privacy\tests\request\content_writer $writer */
|
||||
$writer = writer::with_context($context);
|
||||
$this->assertFalse($writer->has_any_data());
|
||||
@@ -284,6 +295,12 @@ final class provider_test extends provider_testcase {
|
||||
$overrideexport->cutoffdate);
|
||||
$this->assertEquals(\core_privacy\local\request\transform::datetime($overridedata->allowsubmissionsfromdate),
|
||||
$overrideexport->allowsubmissionsfromdate);
|
||||
|
||||
// Check allocated markers and marks are exported.
|
||||
$this->assertEquals($teacher->id, $writer->get_data(['Marker allocations'])->data[0]['marker']);
|
||||
$this->assertEquals($user->id, $writer->get_data(['Marker allocations'])->data[0]['student']);
|
||||
$this->assertEquals($teacher->id, $writer->get_data(['Marks'])->data[0]['marker']);
|
||||
$this->assertEquals(99.9, $writer->get_data(['Marks'])->data[0]['mark']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,7 +323,10 @@ final class provider_test extends provider_testcase {
|
||||
'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
|
||||
'maxattempts' => 3,
|
||||
'assignsubmission_onlinetext_enabled' => true,
|
||||
'assignfeedback_comments_enabled' => true
|
||||
'assignfeedback_comments_enabled' => true,
|
||||
'markingworkflow' => 1,
|
||||
'markingallocation' => 1,
|
||||
'markercount' => 2,
|
||||
]);
|
||||
|
||||
$context = $assign->get_context();
|
||||
@@ -366,6 +386,18 @@ final class provider_test extends provider_testcase {
|
||||
$flagdata->extensionduedate = $duedate;
|
||||
$assign->update_user_flags($flagdata);
|
||||
|
||||
// Allocate the teacher as a marker on the assignment for both users.
|
||||
$assign->update_allocated_markers($user1->id, [$teacher->id]);
|
||||
$assign->update_allocated_markers($user2->id, [$teacher->id]);
|
||||
|
||||
// Give the submission a mark for both users.
|
||||
$gradeobject = $assign->get_user_grade($user1->id, true);
|
||||
$gradeobject->grader = $teacher->id;
|
||||
$assign->update_mark($gradeobject, 11.1);
|
||||
$gradeobject = $assign->get_user_grade($user2->id, true);
|
||||
$gradeobject->grader = $teacher->id;
|
||||
$assign->update_mark($gradeobject, 88.8);
|
||||
|
||||
/** @var \core_privacy\tests\request\content_writer $writer */
|
||||
$writer = writer::with_context($context);
|
||||
$this->assertFalse($writer->has_any_data());
|
||||
@@ -393,6 +425,14 @@ final class provider_test extends provider_testcase {
|
||||
'Feedback comments'])->commenttext);
|
||||
$this->assertStringContainsString($teachercommenttext3, $writer->get_data(['studentsubmissions', $user2->id, 'attempt 2',
|
||||
'Feedback comments'])->commenttext);
|
||||
|
||||
// Check for marker allocations as this teacher.
|
||||
$this->assertEquals($teacher->id, $writer->get_data(['Marker allocations'])->data[0]['marker']);
|
||||
$this->assertEquals($teacher->id, $writer->get_data(['Marker allocations'])->data[1]['marker']);
|
||||
|
||||
// And check for marks they have given.
|
||||
$this->assertEquals($teacher->id, $writer->get_data(['Marks'])->data[0]['marker']);
|
||||
$this->assertEquals($teacher->id, $writer->get_data(['Marks'])->data[1]['marker']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,7 +455,10 @@ final class provider_test extends provider_testcase {
|
||||
'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
|
||||
'maxattempts' => 3,
|
||||
'assignsubmission_onlinetext_enabled' => true,
|
||||
'assignfeedback_comments_enabled' => true
|
||||
'assignfeedback_comments_enabled' => true,
|
||||
'markingworkflow' => 1,
|
||||
'markingallocation' => 1,
|
||||
'markercount' => 2,
|
||||
]);
|
||||
|
||||
$context = $assign->get_context();
|
||||
@@ -478,6 +521,18 @@ final class provider_test extends provider_testcase {
|
||||
// Give the submission a grade.
|
||||
$assign->save_grade($user2->id, $data);
|
||||
|
||||
// Allocate the teacher as a marker on the assignment for both users.
|
||||
$assign->update_allocated_markers($user1->id, [$teacher->id]);
|
||||
$assign->update_allocated_markers($user2->id, [$teacher->id]);
|
||||
|
||||
// Give the submission a mark for both users.
|
||||
$gradeobject = $assign->get_user_grade($user1->id, true);
|
||||
$gradeobject->grader = $teacher->id;
|
||||
$assign->update_mark($gradeobject, 11.1);
|
||||
$gradeobject = $assign->get_user_grade($user2->id, true);
|
||||
$gradeobject->grader = $teacher->id;
|
||||
$assign->update_mark($gradeobject, 88.8);
|
||||
|
||||
// Delete all user data for this assignment.
|
||||
provider::delete_data_for_all_users_in_context($context);
|
||||
|
||||
@@ -490,6 +545,10 @@ final class provider_test extends provider_testcase {
|
||||
$this->assertEmpty($records);
|
||||
$records = $DB->get_records('assignfeedback_comments');
|
||||
$this->assertEmpty($records);
|
||||
$records = $DB->get_records('assign_mark');
|
||||
$this->assertEmpty($records);
|
||||
$records = $DB->get_records('assign_allocated_marker');
|
||||
$this->assertEmpty($records);
|
||||
|
||||
// Check that overrides and the calendar events are deleted.
|
||||
$records = $DB->get_records('event');
|
||||
@@ -520,7 +579,10 @@ final class provider_test extends provider_testcase {
|
||||
'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
|
||||
'maxattempts' => 3,
|
||||
'assignsubmission_onlinetext_enabled' => true,
|
||||
'assignfeedback_comments_enabled' => true
|
||||
'assignfeedback_comments_enabled' => true,
|
||||
'markingworkflow' => 1,
|
||||
'markingallocation' => 1,
|
||||
'markercount' => 2,
|
||||
]);
|
||||
|
||||
$context = $assign->get_context();
|
||||
@@ -583,6 +645,18 @@ final class provider_test extends provider_testcase {
|
||||
// Give the submission a grade.
|
||||
$assign->save_grade($user2->id, $data);
|
||||
|
||||
// Allocate the teacher as a marker on the assignment for both users.
|
||||
$assign->update_allocated_markers($user1->id, [$teacher->id]);
|
||||
$assign->update_allocated_markers($user2->id, [$teacher->id]);
|
||||
|
||||
// Give the submission a mark for both users.
|
||||
$gradeobject = $assign->get_user_grade($user1->id, true);
|
||||
$gradeobject->grader = $teacher->id;
|
||||
$assign->update_mark($gradeobject, 11.1);
|
||||
$gradeobject = $assign->get_user_grade($user2->id, true);
|
||||
$gradeobject->grader = $teacher->id;
|
||||
$assign->update_mark($gradeobject, 88.8);
|
||||
|
||||
// Delete user 2's data.
|
||||
$approvedlist = new approved_contextlist($user2, 'mod_assign', [$context->id, $coursecontext->id]);
|
||||
provider::delete_data_for_user($approvedlist);
|
||||
@@ -621,6 +695,12 @@ final class provider_test extends provider_testcase {
|
||||
$record = array_shift($records);
|
||||
// The remaining event should be for user 1.
|
||||
$this->assertEquals($user1->id, $record->userid);
|
||||
|
||||
// Check marks and allocated markers only exist for user1 now.
|
||||
$records = $DB->get_records('assign_allocated_marker');
|
||||
$this->assertCount(1, $records);
|
||||
$records = $DB->get_records('assign_mark');
|
||||
$this->assertCount(1, $records);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -639,7 +719,7 @@ final class provider_test extends provider_testcase {
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
// User 3 made a submission.
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
// User 4 makes a submission and it is marked by the teacher.
|
||||
// User 4 makes a submission and it is graded by the teacher.
|
||||
$user4 = $this->getDataGenerator()->create_user();
|
||||
// Grading and providing feedback as a teacher.
|
||||
$user5 = $this->getDataGenerator()->create_user();
|
||||
@@ -718,6 +798,25 @@ final class provider_test extends provider_testcase {
|
||||
// Give the submission a grade.
|
||||
$assign2->save_grade($user6->id, $data);
|
||||
|
||||
// Allocate the teacher as a marker on both assignments for some of the users.
|
||||
$assign1->update_allocated_markers($user1->id, [$user5->id]);
|
||||
$assign1->update_allocated_markers($user2->id, [$user5->id]);
|
||||
$assign1->update_allocated_markers($user6->id, [$user5->id]);
|
||||
$assign2->update_allocated_markers($user1->id, [$user5->id]);
|
||||
$assign2->update_allocated_markers($user2->id, [$user5->id]);
|
||||
$assign2->update_allocated_markers($user6->id, [$user5->id]);
|
||||
|
||||
// Give the submission a mark for some users on both assignments.
|
||||
$gradeobject = $assign1->get_user_grade($user3->id, true);
|
||||
$gradeobject->grader = $user5->id;
|
||||
$assign1->update_mark($gradeobject, 10);
|
||||
$gradeobject = $assign1->get_user_grade($user4->id, true);
|
||||
$gradeobject->grader = $user5->id;
|
||||
$assign1->update_mark($gradeobject, 100);
|
||||
$gradeobject = $assign2->get_user_grade($user6->id, true);
|
||||
$gradeobject->grader = $user5->id;
|
||||
$assign2->update_mark($gradeobject, 59);
|
||||
|
||||
// Check data is in place.
|
||||
$data = $DB->get_records('assign_submission');
|
||||
// We should have one entry for user 3 and two entries each for user 4 and 6.
|
||||
@@ -735,8 +834,8 @@ final class provider_test extends provider_testcase {
|
||||
$this->assertEquals(2, $usercounts[$user6->id]);
|
||||
|
||||
$data = $DB->get_records('assign_grades');
|
||||
// Two entries in assign_grades, one for each grade given.
|
||||
$this->assertCount(2, $data);
|
||||
// Three entries in assign_grades, for grades given and grade items created for setting marks.
|
||||
$this->assertCount(3, $data);
|
||||
|
||||
$data = $DB->get_records('assign_overrides');
|
||||
$this->assertCount(1, $data);
|
||||
@@ -744,6 +843,12 @@ final class provider_test extends provider_testcase {
|
||||
$data = $DB->get_records('comments');
|
||||
$this->assertCount(1, $data);
|
||||
|
||||
$data = $DB->get_records('assign_allocated_marker');
|
||||
$this->assertCount(6, $data);
|
||||
|
||||
$data = $DB->get_records('assign_mark');
|
||||
$this->assertCount(3, $data);
|
||||
|
||||
$userlist = new \core_privacy\local\request\approved_userlist($context, 'assign', [$user1->id, $user2->id]);
|
||||
provider::delete_data_for_users($userlist);
|
||||
|
||||
@@ -757,6 +862,14 @@ final class provider_test extends provider_testcase {
|
||||
// No change here.
|
||||
$this->assertCount(5, $data);
|
||||
|
||||
// Should be 4 records left - user6 on assign1, and everyone on assign2.
|
||||
$data = $DB->get_records('assign_allocated_marker');
|
||||
$this->assertCount(4, $data);
|
||||
|
||||
// Should be unchanged.
|
||||
$data = $DB->get_records('assign_mark');
|
||||
$this->assertCount(3, $data);
|
||||
|
||||
$userlist = new \core_privacy\local\request\approved_userlist($context, 'assign', [$user3->id, $user5->id]);
|
||||
provider::delete_data_for_users($userlist);
|
||||
|
||||
@@ -767,5 +880,13 @@ final class provider_test extends provider_testcase {
|
||||
$data = $DB->get_records('assign_grades');
|
||||
// Grades should be unchanged.
|
||||
$this->assertCount(2, $data);
|
||||
|
||||
// Should be 3 left, all for assign2.
|
||||
$data = $DB->get_records('assign_allocated_marker');
|
||||
$this->assertCount(3, $data);
|
||||
|
||||
// Should be 1 left, for assign2.
|
||||
$data = $DB->get_records('assign_mark');
|
||||
$this->assertCount(1, $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,5 +25,5 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'mod_assign'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->version = 2026022300; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2026022301; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2025092600; // Requires this Moodle version.
|
||||
|
||||
Reference in New Issue
Block a user