first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
Marking Guide grading form written by Dan Marsden <dan@danmarsden.com>
based on Lightwork Rubric type 2 format and the spec available here:
http://docs.moodle.org/dev/Lightwork
@@ -0,0 +1,10 @@
/**
* AMD code for the frequently used comments chooser for the marking guide grading form.
*
* @module gradingform_guide/comment_chooser
* @copyright 2015 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("gradingform_guide/comment_chooser",["jquery","core/templates","core/key_codes","core/notification","core/yui"],(function($,templates,keycodes,notification){return{initialise:function(criterionId,buttonId,remarkId,commentOptions){function generateCommentsChooser(){var context={criterionId:criterionId,comments:commentOptions};templates.render("gradingform_guide/comment_chooser",context).done((function(compiledSource){!function(compiledSource,comments){var titleLabel="<label>"+M.util.get_string("insertcomment","gradingform_guide")+"</label>",cancelButtonId="comment-chooser-"+criterionId+"-cancel",cancelButton='<button id="'+cancelButtonId+'">'+M.util.get_string("cancel","moodle")+"</button>",chooserDialog=new M.core.dialogue({modal:!0,headerContent:titleLabel,bodyContent:compiledSource,footerContent:cancelButton,focusAfterHide:"#"+remarkId,id:"comments-chooser-dialog-"+criterionId});$("#"+cancelButtonId).click((function(){chooserDialog.hide()})),$.each(comments,(function(index,comment){var commentOptionId="#comment-option-"+criterionId+"-"+comment.id;$(commentOptionId).click((function(){var remarkTextArea=$("#"+remarkId),remarkText=remarkTextArea.val();""!==remarkText.trim()&&(remarkText+="\n"),remarkText+=comment.description,remarkTextArea.val(remarkText),chooserDialog.hide()})),$(document).off("keypress",commentOptionId).on("keypress",commentOptionId,(function(){(event.which||event.keyCode)===keycodes.space&&$(commentOptionId).click()}))})),chooserDialog.after("visibleChange",(function(e){e.prevVal&&!e.newVal&&this.destroy()}),chooserDialog),chooserDialog.show()}(compiledSource,commentOptions)})).fail(notification.exception)}$("#"+buttonId).click((function(e){e.preventDefault(),generateCommentsChooser()}))}}}));
//# sourceMappingURL=comment_chooser.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
define("gradingform_guide/grades/grader/gradingpanel",["exports","core/ajax","core_grades/grades/grader/gradingpanel/normalise","core_grades/grades/grader/gradingpanel/comparison","jquery"],(function(_exports,_ajax,_normalise,_comparison,_jquery){var obj;
/**
* Grading panel for gradingform_guide.
*
* @module gradingform_guide/grades/grader/gradingpanel
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.storeCurrentGrade=_exports.fetchCurrentGrade=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};_exports.fetchCurrentGrade=(component,contextid,itemname,gradeduserid)=>(0,_ajax.call)([{methodname:"gradingform_guide_grader_gradingpanel_fetch",args:{component:component,contextid:contextid,itemname:itemname,gradeduserid:gradeduserid}}])[0];_exports.storeCurrentGrade=async(component,contextid,itemname,gradeduserid,notifyUser,rootNode)=>{const form=rootNode.querySelector("form");return!0===(0,_comparison.compareData)(form)?(0,_normalise.normaliseResult)(await(0,_ajax.call)([{methodname:"gradingform_guide_grader_gradingpanel_store",args:{component:component,contextid:contextid,itemname:itemname,gradeduserid:gradeduserid,notifyuser:notifyUser,formdata:(0,_jquery.default)(form).serialize()}}])[0]):""}}));
//# sourceMappingURL=gradingpanel.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"gradingpanel.min.js","sources":["../../../src/grades/grader/gradingpanel.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 * Grading panel for gradingform_guide.\n *\n * @module gradingform_guide/grades/grader/gradingpanel\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';\n\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n *\n * @returns {Promise}\n */\nexport const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `gradingform_guide_grader_gradingpanel_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n * @param {Boolean} notifyUser\n * @param {HTMLElement} rootNode\n *\n * @returns {Promise}\n */\nexport const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, notifyUser, rootNode) => {\n const form = rootNode.querySelector('form');\n\n if (compareData(form) === true) {\n return normaliseResult(await fetchMany([{\n methodname: `gradingform_guide_grader_gradingpanel_store`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n notifyuser: notifyUser,\n formdata: jQuery(form).serialize(),\n },\n }])[0]);\n } else {\n return '';\n }\n};\n"],"names":["component","contextid","itemname","gradeduserid","methodname","args","async","notifyUser","rootNode","form","querySelector","notifyuser","formdata","serialize"],"mappings":";;;;;;;6MAwCiC,CAACA,UAAWC,UAAWC,SAAUC,gBACvD,cAAU,CAAC,CACdC,yDACAC,KAAM,CACFL,UAAAA,UACAC,UAAAA,UACAC,SAAAA,SACAC,aAAAA,iBAEJ,8BAeyBG,MAAMN,UAAWC,UAAWC,SAAUC,aAAcI,WAAYC,kBACvFC,KAAOD,SAASE,cAAc,eAEV,KAAtB,2BAAYD,OACL,oCAAsB,cAAU,CAAC,CACpCL,yDACAC,KAAM,CACFL,UAAAA,UACAC,UAAAA,UACAC,SAAAA,SACAC,aAAAA,aACAQ,WAAYJ,WACZK,UAAU,mBAAOH,MAAMI,gBAE3B,IAEG"}
@@ -0,0 +1,10 @@
define("gradingform_guide/grades/grader/gradingpanel/comments",["exports","./comments/selectors"],(function(_exports,_selectors){var obj;
/**
* Grading panel frequently used comments selector.
*
* @module gradingform_guide/grades/grader/gradingpanel/comments
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj};_exports.init=rootId=>{document.querySelector("#".concat(rootId)).addEventListener("click",(e=>{if(!e.target.matches(_selectors.default.frequentComment))return;e.preventDefault();const clicked=e.target.closest(_selectors.default.frequentComment),remark=clicked.closest(_selectors.default.criterion).querySelector(_selectors.default.remark);remark&&(remark.value.trim()?remark.value+="\n".concat(clicked.innerHTML):remark.value+=clicked.innerHTML)}))}}));
//# sourceMappingURL=comments.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"comments.min.js","sources":["../../../../src/grades/grader/gradingpanel/comments.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 * Grading panel frequently used comments selector.\n *\n * @module gradingform_guide/grades/grader/gradingpanel/comments\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Selectors from './comments/selectors';\n\n/**\n * Manage the frequently used comments in the Marking Guide form.\n *\n * @param {String} rootId\n */\nexport const init = (rootId) => {\n const rootNode = document.querySelector(`#${rootId}`);\n\n rootNode.addEventListener('click', (e) => {\n if (!e.target.matches(Selectors.frequentComment)) {\n return;\n }\n\n e.preventDefault();\n\n const clicked = e.target.closest(Selectors.frequentComment);\n const criterion = clicked.closest(Selectors.criterion);\n const remark = criterion.querySelector(Selectors.remark);\n\n if (!remark) {\n return;\n }\n\n // Either append the comment to an existing comment or set it as the comment.\n if (remark.value.trim()) {\n remark.value += `\\n${clicked.innerHTML}`;\n } else {\n remark.value += clicked.innerHTML;\n }\n });\n};\n"],"names":["rootId","document","querySelector","addEventListener","e","target","matches","Selectors","frequentComment","preventDefault","clicked","closest","remark","criterion","value","trim","innerHTML"],"mappings":";;;;;;;8JA8BqBA,SACAC,SAASC,yBAAkBF,SAEnCG,iBAAiB,SAAUC,QAC3BA,EAAEC,OAAOC,QAAQC,mBAAUC,wBAIhCJ,EAAEK,uBAEIC,QAAUN,EAAEC,OAAOM,QAAQJ,mBAAUC,iBAErCI,OADYF,QAAQC,QAAQJ,mBAAUM,WACnBX,cAAcK,mBAAUK,QAE5CA,SAKDA,OAAOE,MAAMC,OACbH,OAAOE,mBAAcJ,QAAQM,WAE7BJ,OAAOE,OAASJ,QAAQM"}
@@ -0,0 +1,3 @@
define("gradingform_guide/grades/grader/gradingpanel/comments/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={frequentComment:'[data-gradingform_guide-role="frequent-comment"]',criterion:'[data-gradingform-guide-role="criterion"]',remark:'[data-gradingform-guide-role="remark"]'},_exports.default}));
//# sourceMappingURL=selectors.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"selectors.min.js","sources":["../../../../../src/grades/grader/gradingpanel/comments/selectors.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 * Define all of the selectors we will be using on the Marking Guide interface.\n *\n * @module gradingform_guide/grades/grader/gradingpanel/comments/selectors\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n frequentComment: '[data-gradingform_guide-role=\"frequent-comment\"]',\n criterion: '[data-gradingform-guide-role=\"criterion\"]',\n remark: '[data-gradingform-guide-role=\"remark\"]',\n};\n"],"names":["frequentComment","criterion","remark"],"mappings":"iNAsBe,CACXA,gBAAiB,mDACjBC,UAAW,4CACXC,OAAQ"}
@@ -0,0 +1,138 @@
// 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/>.
/**
* AMD code for the frequently used comments chooser for the marking guide grading form.
*
* @module gradingform_guide/comment_chooser
* @copyright 2015 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/templates', 'core/key_codes', 'core/notification', 'core/yui'],
function($, templates, keycodes, notification) {
// Private variables and functions.
return /** @alias module:gradingform_guide/comment_chooser */ {
// Public variables and functions.
/**
* Initialises the module.
*
* Basically, it performs the binding and handling of the button click event for
* the 'Insert frequently used comment' button.
*
* @param {Integer} criterionId The criterion ID.
* @param {String} buttonId The element ID of the button which the handler will be bound to.
* @param {String} remarkId The element ID of the remark text area where the text of the selected comment will be copied to.
* @param {Array} commentOptions The array of frequently used comments to be used as options.
*/
initialise: function(criterionId, buttonId, remarkId, commentOptions) {
/**
* Display the chooser dialog using the compiled HTML from the mustache template
* and binds onclick events for the generated comment options.
*
* @param {String} compiledSource The compiled HTML from the mustache template
* @param {Array} comments Array containing comments.
*/
function displayChooserDialog(compiledSource, comments) {
var titleLabel = '<label>' + M.util.get_string('insertcomment', 'gradingform_guide') + '</label>';
var cancelButtonId = 'comment-chooser-' + criterionId + '-cancel';
var cancelButton = '<button id="' + cancelButtonId + '">' + M.util.get_string('cancel', 'moodle') + '</button>';
// Set dialog's body content.
var chooserDialog = new M.core.dialogue({
modal: true,
headerContent: titleLabel,
bodyContent: compiledSource,
footerContent: cancelButton,
focusAfterHide: '#' + remarkId,
id: "comments-chooser-dialog-" + criterionId
});
// Bind click event to the cancel button.
$("#" + cancelButtonId).click(function() {
chooserDialog.hide();
});
// Loop over each comment item and bind click events.
$.each(comments, function(index, comment) {
var commentOptionId = '#comment-option-' + criterionId + '-' + comment.id;
// Delegate click event for the generated option link.
$(commentOptionId).click(function() {
var remarkTextArea = $('#' + remarkId);
var remarkText = remarkTextArea.val();
// Add line break if the current value of the remark text is not empty.
if (remarkText.trim() !== '') {
remarkText += '\n';
}
remarkText += comment.description;
remarkTextArea.val(remarkText);
chooserDialog.hide();
});
// Handle keypress on list items.
$(document).off('keypress', commentOptionId).on('keypress', commentOptionId, function() {
var keyCode = event.which || event.keyCode;
// Trigger click event when user presses space.
if (keyCode === keycodes.space) {
$(commentOptionId).click();
}
});
});
// Destroy the dialog when it is hidden to allow the grading section to
// be loaded as a fragment multiple times within the same page.
chooserDialog.after('visibleChange', function(e) {
// Going from visible to hidden.
if (e.prevVal && !e.newVal) {
this.destroy();
}
}, chooserDialog);
// Show dialog.
chooserDialog.show();
}
/**
* Generates the comments chooser dialog from the grading_form/comment_chooser mustache template.
*/
function generateCommentsChooser() {
// Template context.
var context = {
criterionId: criterionId,
comments: commentOptions
};
// Render the template and display the comment chooser dialog.
templates.render('gradingform_guide/comment_chooser', context)
.done(function(compiledSource) {
displayChooserDialog(compiledSource, commentOptions);
})
.fail(notification.exception);
}
// Bind click event for the comments chooser button.
$("#" + buttonId).click(function(e) {
e.preventDefault();
generateCommentsChooser();
});
}
};
});
@@ -0,0 +1,83 @@
// 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/>.
/**
* Grading panel for gradingform_guide.
*
* @module gradingform_guide/grades/grader/gradingpanel
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
import {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';
import {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';
// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()
import jQuery from 'jquery';
/**
* For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.
*
* @param {String} component
* @param {Number} contextid
* @param {String} itemname
* @param {Number} gradeduserid
*
* @returns {Promise}
*/
export const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {
return fetchMany([{
methodname: `gradingform_guide_grader_gradingpanel_fetch`,
args: {
component,
contextid,
itemname,
gradeduserid,
},
}])[0];
};
/**
* For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.
*
* @param {String} component
* @param {Number} contextid
* @param {String} itemname
* @param {Number} gradeduserid
* @param {Boolean} notifyUser
* @param {HTMLElement} rootNode
*
* @returns {Promise}
*/
export const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, notifyUser, rootNode) => {
const form = rootNode.querySelector('form');
if (compareData(form) === true) {
return normaliseResult(await fetchMany([{
methodname: `gradingform_guide_grader_gradingpanel_store`,
args: {
component,
contextid,
itemname,
gradeduserid,
notifyuser: notifyUser,
formdata: jQuery(form).serialize(),
},
}])[0]);
} else {
return '';
}
};
@@ -0,0 +1,56 @@
// 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/>.
/**
* Grading panel frequently used comments selector.
*
* @module gradingform_guide/grades/grader/gradingpanel/comments
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Selectors from './comments/selectors';
/**
* Manage the frequently used comments in the Marking Guide form.
*
* @param {String} rootId
*/
export const init = (rootId) => {
const rootNode = document.querySelector(`#${rootId}`);
rootNode.addEventListener('click', (e) => {
if (!e.target.matches(Selectors.frequentComment)) {
return;
}
e.preventDefault();
const clicked = e.target.closest(Selectors.frequentComment);
const criterion = clicked.closest(Selectors.criterion);
const remark = criterion.querySelector(Selectors.remark);
if (!remark) {
return;
}
// Either append the comment to an existing comment or set it as the comment.
if (remark.value.trim()) {
remark.value += `\n${clicked.innerHTML}`;
} else {
remark.value += clicked.innerHTML;
}
});
};
@@ -0,0 +1,27 @@
// 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/>.
/**
* Define all of the selectors we will be using on the Marking Guide interface.
*
* @module gradingform_guide/grades/grader/gradingpanel/comments/selectors
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
frequentComment: '[data-gradingform_guide-role="frequent-comment"]',
criterion: '[data-gradingform-guide-role="criterion"]',
remark: '[data-gradingform-guide-role="remark"]',
};
@@ -0,0 +1,122 @@
<?php
// 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/>.
/**
* Support for backup API
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Defines marking guide backup structures
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_gradingform_guide_plugin extends backup_gradingform_plugin {
/**
* Declares marking guide structures to append to the grading form definition
* @return backup_plugin_element
*/
protected function define_definition_plugin_structure() {
// Append data only if the grand-parent element has 'method' set to 'guide'.
$plugin = $this->get_plugin_element(null, '../../method', 'guide');
// Create a visible container for our data.
$pluginwrapper = new backup_nested_element($this->get_recommended_name());
// Connect our visible container to the parent.
$plugin->add_child($pluginwrapper);
// Define our elements.
$criteria = new backup_nested_element('guidecriteria');
$criterion = new backup_nested_element('guidecriterion', array('id'), array(
'sortorder', 'shortname', 'description', 'descriptionformat',
'descriptionmarkers', 'descriptionmarkersformat', 'maxscore'));
$comments = new backup_nested_element('guidecomments');
$comment = new backup_nested_element('guidecomment', array('id'), array(
'sortorder', 'description', 'descriptionformat'));
// Build elements hierarchy.
$pluginwrapper->add_child($criteria);
$criteria->add_child($criterion);
$pluginwrapper->add_child($comments);
$comments->add_child($comment);
// Set sources to populate the data.
$criterion->set_source_table('gradingform_guide_criteria',
array('definitionid' => backup::VAR_PARENTID));
$comment->set_source_table('gradingform_guide_comments',
array('definitionid' => backup::VAR_PARENTID));
// No need to annotate ids or files yet (one day when criterion definition supports
// embedded files, they must be annotated here).
return $plugin;
}
/**
* Declares marking guide structures to append to the grading form instances
* @return backup_plugin_element
*/
protected function define_instance_plugin_structure() {
// Append data only if the ancestor 'definition' element has 'method' set to 'guide'.
$plugin = $this->get_plugin_element(null, '../../../../method', 'guide');
// Create a visible container for our data.
$pluginwrapper = new backup_nested_element($this->get_recommended_name());
// Connect our visible container to the parent.
$plugin->add_child($pluginwrapper);
// Define our elements.
$fillings = new backup_nested_element('fillings');
$filling = new backup_nested_element('filling', array('id'), array(
'criterionid', 'remark', 'remarkformat', 'score'));
// Build elements hierarchy.
$pluginwrapper->add_child($fillings);
$fillings->add_child($filling);
// Set sources to populate the data.
$filling->set_source_table('gradingform_guide_fillings',
array('instanceid' => backup::VAR_PARENTID));
// No need to annotate ids or files yet (one day when remark field supports
// embedded fileds, they must be annotated here).
return $plugin;
}
}
@@ -0,0 +1,135 @@
<?php
// 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/>.
/**
* Support for restore API
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Restores the marking guide specific data from grading.xml file
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_gradingform_guide_plugin extends restore_gradingform_plugin {
/**
* Declares the marking guide XML paths attached to the form definition element
*
* @return array of {@link restore_path_element}
*/
protected function define_definition_plugin_structure() {
$paths = array();
$paths[] = new restore_path_element('gradingform_guide_criterion',
$this->get_pathfor('/guidecriteria/guidecriterion'));
$paths[] = new restore_path_element('gradingform_guide_comment',
$this->get_pathfor('/guidecomments/guidecomment'));
// MDL-37714: Correctly locate frequent used comments in both the
// current and incorrect old format.
$paths[] = new restore_path_element('gradingform_guide_comment_legacy',
$this->get_pathfor('/guidecriteria/guidecomments/guidecomment'));
return $paths;
}
/**
* Declares the marking guide XML paths attached to the form instance element
*
* @return array of {@link restore_path_element}
*/
protected function define_instance_plugin_structure() {
$paths = array();
$paths[] = new restore_path_element('gradinform_guide_filling',
$this->get_pathfor('/fillings/filling'));
return $paths;
}
/**
* Processes criterion element data
*
* Sets the mapping 'gradingform_guide_criterion' to be used later by
* {@link self::process_gradinform_guide_filling()}
*
* @param array|stdClass $data
*/
public function process_gradingform_guide_criterion($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->definitionid = $this->get_new_parentid('grading_definition');
$newid = $DB->insert_record('gradingform_guide_criteria', $data);
$this->set_mapping('gradingform_guide_criterion', $oldid, $newid);
}
/**
* Processes comments element data
*
* @param array|stdClass $data The data to insert as a comment
*/
public function process_gradingform_guide_comment($data) {
global $DB;
$data = (object)$data;
$data->definitionid = $this->get_new_parentid('grading_definition');
$DB->insert_record('gradingform_guide_comments', $data);
}
/**
* Processes comments element data
*
* @param array|stdClass $data The data to insert as a comment
*/
public function process_gradingform_guide_comment_legacy($data) {
global $DB;
$data = (object)$data;
$data->definitionid = $this->get_new_parentid('grading_definition');
$DB->insert_record('gradingform_guide_comments', $data);
}
/**
* Processes filling element data
*
* @param array|stdClass $data The data to insert as a filling
*/
public function process_gradinform_guide_filling($data) {
global $DB;
$data = (object)$data;
$data->instanceid = $this->get_new_parentid('grading_instance');
$data->criterionid = $this->get_mappingid('gradingform_guide_criterion', $data->criterionid);
$DB->insert_record('gradingform_guide_fillings', $data);
}
}
@@ -0,0 +1,321 @@
<?php
// 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/>.
/**
* Web services relating to fetching of a marking guide for the grading panel.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace gradingform_guide\grades\grader\gradingpanel\external;
global $CFG;
use coding_exception;
use context;
use core_user;
use core_grades\component_gradeitem as gradeitem;
use core_grades\component_gradeitems;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use moodle_exception;
use stdClass;
require_once($CFG->dirroot.'/grade/grading/form/guide/lib.php');
/**
* Web services relating to fetching of a marking guide for the grading panel.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class fetch extends external_api {
/**
* Describes the parameters for fetching the grading panel for a simple grade.
*
* @return external_function_parameters
* @since Moodle 3.8
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters ([
'component' => new external_value(
PARAM_ALPHANUMEXT,
'The name of the component',
VALUE_REQUIRED
),
'contextid' => new external_value(
PARAM_INT,
'The ID of the context being graded',
VALUE_REQUIRED
),
'itemname' => new external_value(
PARAM_ALPHANUM,
'The grade item itemname being graded',
VALUE_REQUIRED
),
'gradeduserid' => new external_value(
PARAM_INT,
'The ID of the user show',
VALUE_REQUIRED
),
]);
}
/**
* Fetch the data required to build a grading panel for a simple grade.
*
* @param string $component
* @param int $contextid
* @param string $itemname
* @param int $gradeduserid
* @return array
* @throws \dml_exception
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
* @throws coding_exception
* @throws moodle_exception
* @since Moodle 3.8
*/
public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid): array {
global $CFG, $USER;
require_once("{$CFG->libdir}/gradelib.php");
[
'component' => $component,
'contextid' => $contextid,
'itemname' => $itemname,
'gradeduserid' => $gradeduserid,
] = self::validate_parameters(self::execute_parameters(), [
'component' => $component,
'contextid' => $contextid,
'itemname' => $itemname,
'gradeduserid' => $gradeduserid,
]);
// Validate the context.
$context = context::instance_by_id($contextid);
self::validate_context($context);
// Validate that the supplied itemname is a gradable item.
if (!component_gradeitems::is_valid_itemname($component, $itemname)) {
throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component");
}
// Fetch the gradeitem instance.
$gradeitem = gradeitem::instance($component, $context, $itemname);
if (MARKING_GUIDE !== $gradeitem->get_advanced_grading_method()) {
throw new moodle_exception(
"The {$itemname} item in {$component}/{$contextid} is not configured for advanced grading with a marking guide"
);
}
// Fetch the actual data.
$gradeduser = core_user::get_user($gradeduserid, '*', MUST_EXIST);
// One can access its own grades. Others just if they're graders.
if ($gradeduserid != $USER->id) {
$gradeitem->require_user_can_grade($gradeduser, $USER);
}
return self::get_fetch_data($gradeitem, $gradeduser);
}
/**
* Get the data to be fetched.
*
* @param gradeitem $gradeitem
* @param stdClass $gradeduser
* @return array
*/
public static function get_fetch_data(gradeitem $gradeitem, stdClass $gradeduser): array {
global $USER;
$hasgrade = $gradeitem->user_has_grade($gradeduser);
$grade = $gradeitem->get_formatted_grade_for_user($gradeduser, $USER);
$instance = $gradeitem->get_advanced_grading_instance($USER, $grade);
if (!$instance) {
throw new moodle_exception('error:gradingunavailable', 'grading');
}
$controller = $instance->get_controller();
$definition = $controller->get_definition();
$fillings = $instance->get_guide_filling();
$context = $controller->get_context();
$definitionid = (int) $definition->id;
// Set up some items we need to return on other interfaces.
$gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]);
$gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null;
$maxgrade = max(array_keys($controller->get_grade_range()));
$criterion = [];
if ($definition->guide_criteria) {
$criterion = array_map(function($criterion) use ($definitionid, $fillings, $context) {
$result = [
'id' => $criterion['id'],
'name' => $criterion['shortname'],
'maxscore' => $criterion['maxscore'],
'description' => self::get_formatted_text(
$context,
$definitionid,
'description',
$criterion['description'],
(int) $criterion['descriptionformat']
),
'descriptionmarkers' => self::get_formatted_text(
$context,
$definitionid,
'descriptionmarkers',
$criterion['descriptionmarkers'],
(int) $criterion['descriptionmarkersformat']
),
'score' => null,
'remark' => null,
];
if (array_key_exists($criterion['id'], $fillings['criteria'])) {
$filling = $fillings['criteria'][$criterion['id']];
$result['score'] = $filling['score'];
$result['remark'] = self::get_formatted_text(
$context,
$definitionid,
'remark',
$filling['remark'],
(int) $filling['remarkformat']
);
}
return $result;
}, $definition->guide_criteria);
}
$comments = [];
if ($definition->guide_comments) {
$comments = array_map(function($comment) use ($definitionid, $context) {
return [
'id' => $comment['id'],
'sortorder' => $comment['sortorder'],
'description' => self::get_formatted_text(
$context,
$definitionid,
'description',
$comment['description'],
(int) $comment['descriptionformat']
),
];
}, $definition->guide_comments);
}
return [
'templatename' => 'gradingform_guide/grades/grader/gradingpanel',
'hasgrade' => $hasgrade,
'grade' => [
'instanceid' => $instance->get_id(),
'criterion' => $criterion,
'hascomments' => !empty($comments),
'comments' => $comments,
'usergrade' => $grade->usergrade,
'maxgrade' => $maxgrade,
'gradedby' => $gradername,
'timecreated' => $grade->timecreated,
'timemodified' => $grade->timemodified,
],
'warnings' => [],
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
* @since Moodle 3.8
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'templatename' => new external_value(PARAM_SAFEPATH, 'The template to use when rendering this data'),
'hasgrade' => new external_value(PARAM_BOOL, 'Does the user have a grade?'),
'grade' => new external_single_structure([
'instanceid' => new external_value(PARAM_INT, 'The id of the current grading instance'),
'criterion' => new external_multiple_structure(
new external_single_structure([
'id' => new external_value(PARAM_INT, 'The id of the criterion'),
'name' => new external_value(PARAM_RAW, 'The name of the criterion'),
'maxscore' => new external_value(PARAM_FLOAT, 'The maximum score for this criterion'),
'description' => new external_value(PARAM_RAW, 'The description of the criterion'),
'descriptionmarkers' => new external_value(PARAM_RAW, 'The description of the criterion for markers'),
'score' => new external_value(PARAM_FLOAT, 'The current score for user being assessed', VALUE_OPTIONAL),
'remark' => new external_value(PARAM_RAW, 'Any remarks for this criterion for the user being assessed', VALUE_OPTIONAL),
]),
'The criterion by which this item will be graded'
),
'hascomments' => new external_value(PARAM_BOOL, 'Whether there are any frequently-used comments'),
'comments' => new external_multiple_structure(
new external_single_structure([
'id' => new external_value(PARAM_INT, 'Comment id'),
'sortorder' => new external_value(PARAM_INT, 'The sortorder of this comment'),
'description' => new external_value(PARAM_RAW, 'The comment value'),
]),
'Frequently used comments'
),
'usergrade' => new external_value(PARAM_RAW, 'Current user grade'),
'maxgrade' => new external_value(PARAM_RAW, 'Max possible grade'),
'gradedby' => new external_value(PARAM_RAW, 'The assumed grader of this grading instance'),
'timecreated' => new external_value(PARAM_INT, 'The time that the grade was created'),
'timemodified' => new external_value(PARAM_INT, 'The time that the grade was last updated'),
]),
'warnings' => new external_warnings(),
]);
}
/**
* Get a formatted version of the remark/description/etc.
*
* @param context $context
* @param int $definitionid
* @param string $filearea The file area of the field
* @param string $text The text to be formatted
* @param int $format The input format of the string
* @return string
*/
protected static function get_formatted_text(context $context, int $definitionid, string $filearea, string $text, int $format): string {
$formatoptions = [
'noclean' => false,
'trusted' => false,
'filter' => true,
];
[$newtext] = \core_external\util::format_text(
$text,
$format,
$context,
'grading',
$filearea,
$definitionid,
$formatoptions
);
return $newtext;
}
}
@@ -0,0 +1,186 @@
<?php
// 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/>.
/**
* Web services relating to fetching of a marking guide for the grading panel.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace gradingform_guide\grades\grader\gradingpanel\external;
global $CFG;
use coding_exception;
use context;
use core_grades\component_gradeitem as gradeitem;
use core_grades\component_gradeitems;
use core_user;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use moodle_exception;
require_once($CFG->dirroot.'/grade/grading/form/guide/lib.php');
/**
* Web services relating to storing of a marking guide for the grading panel.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class store extends external_api {
/**
* Describes the parameters for storing the grading panel for a simple grade.
*
* @return external_function_parameters
* @since Moodle 3.8
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters ([
'component' => new external_value(
PARAM_ALPHANUMEXT,
'The name of the component',
VALUE_REQUIRED
),
'contextid' => new external_value(
PARAM_INT,
'The ID of the context being graded',
VALUE_REQUIRED
),
'itemname' => new external_value(
PARAM_ALPHANUM,
'The grade item itemname being graded',
VALUE_REQUIRED
),
'gradeduserid' => new external_value(
PARAM_INT,
'The ID of the user show',
VALUE_REQUIRED
),
'notifyuser' => new external_value(
PARAM_BOOL,
'Wheteher to notify the user or not',
VALUE_DEFAULT,
false
),
'formdata' => new external_value(
PARAM_RAW,
'The serialised form data representing the grade',
VALUE_REQUIRED
),
]);
}
/**
* Fetch the data required to build a grading panel for a simple grade.
*
* @param string $component
* @param int $contextid
* @param string $itemname
* @param int $gradeduserid
* @param bool $notifyuser
* @param string $formdata
*
* @return array
* @throws \dml_exception
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
* @throws coding_exception
* @throws moodle_exception
* @since Moodle 3.8
*/
public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid,
bool $notifyuser, string $formdata): array {
global $USER;
[
'component' => $component,
'contextid' => $contextid,
'itemname' => $itemname,
'gradeduserid' => $gradeduserid,
'notifyuser' => $notifyuser,
'formdata' => $formdata,
] = self::validate_parameters(self::execute_parameters(), [
'component' => $component,
'contextid' => $contextid,
'itemname' => $itemname,
'gradeduserid' => $gradeduserid,
'notifyuser' => $notifyuser,
'formdata' => $formdata,
]);
// Validate the context.
$context = context::instance_by_id($contextid);
self::validate_context($context);
// Validate that the supplied itemname is a gradable item.
if (!component_gradeitems::is_valid_itemname($component, $itemname)) {
throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component");
}
// Fetch the gradeitem instance.
$gradeitem = gradeitem::instance($component, $context, $itemname);
// Validate that this gradeitem is actually enabled.
if (!$gradeitem->is_grading_enabled()) {
throw new moodle_exception("Grading is not enabled for {$itemname} in this context");
}
// Fetch the record for the graded user.
$gradeduser = core_user::get_user($gradeduserid);
// Require that this user can save grades.
$gradeitem->require_user_can_grade($gradeduser, $USER);
if (MARKING_GUIDE !== $gradeitem->get_advanced_grading_method()) {
throw new moodle_exception(
"The {$itemname} item in {$component}/{$contextid} is not configured for advanced grading with a marking guide"
);
}
// Parse the serialised string into an object.
$data = [];
parse_str($formdata, $data);
// Grade.
$gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data);
// Notify.
if ($notifyuser) {
// Send notification.
$gradeitem->send_student_notification($gradeduser, $USER);
}
return fetch::get_fetch_data($gradeitem, $gradeduser);
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
* @since Moodle 3.8
*/
public static function execute_returns(): external_single_structure {
return fetch::execute_returns();
}
}
@@ -0,0 +1,129 @@
<?php
// 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/>.
/**
* Privacy class for requesting user data.
*
* @package gradingform_guide
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradingform_guide\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\writer;
/**
* Privacy class for requesting user data.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_grading\privacy\gradingform_provider_v2,
\core_privacy\local\request\user_preference_provider {
/**
* Return the fields which contain personal data.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('gradingform_guide_fillings', [
'instanceid' => 'privacy:metadata:instanceid',
'criterionid' => 'privacy:metadata:criterionid',
'remark' => 'privacy:metadata:remark',
'score' => 'privacy:metadata:score'
], 'privacy:metadata:fillingssummary');
$collection->add_user_preference(
'gradingform_guide-showmarkerdesc',
'privacy:metadata:preference:showmarkerdesc'
);
$collection->add_user_preference(
'gradingform_guide-showstudentdesc',
'privacy:metadata:preference:showstudentdesc'
);
return $collection;
}
/**
* Export user data relating to an instance ID.
*
* @param \context $context Context to use with the export writer.
* @param int $instanceid The instance ID to export data for.
* @param array $subcontext The directory to export this data to.
*/
public static function export_gradingform_instance_data(\context $context, int $instanceid, array $subcontext) {
global $DB;
// Get records from the provided params.
$params = ['instanceid' => $instanceid];
$sql = "SELECT gc.shortname, gc.description, gc.maxscore, gf.score, gf.remark
FROM {gradingform_guide_fillings} gf
JOIN {gradingform_guide_criteria} gc ON gc.id = gf.criterionid
WHERE gf.instanceid = :instanceid";
$records = $DB->get_records_sql($sql, $params);
if ($records) {
$subcontext = array_merge($subcontext, [get_string('guide', 'gradingform_guide'), $instanceid]);
writer::with_context($context)->export_data($subcontext, (object) $records);
}
}
/**
* Deletes all user data related to the provided instance IDs.
*
* @param array $instanceids The instance IDs to delete information from.
*/
public static function delete_gradingform_for_instances(array $instanceids) {
global $DB;
$DB->delete_records_list('gradingform_guide_fillings', 'instanceid', $instanceids);
}
/**
* Store all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$prefvalue = get_user_preferences('gradingform_guide-showmarkerdesc', null, $userid);
if ($prefvalue !== null) {
$transformedvalue = transform::yesno($prefvalue);
writer::export_user_preference(
'gradingform_guide',
'gradingform_guide-showmarkerdesc',
$transformedvalue,
get_string('privacy:metadata:preference:showmarkerdesc', 'gradingform_guide')
);
}
$prefvalue = get_user_preferences('gradingform_guide-showstudentdesc', null, $userid);
if ($prefvalue !== null) {
$transformedvalue = transform::yesno($prefvalue);
writer::export_user_preference(
'gradingform_guide',
'gradingform_guide-showstudentdesc',
$transformedvalue,
get_string('privacy:metadata:preference:showstudentdesc', 'gradingform_guide')
);
}
}
}
+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="grade/grading/form/guide/db" VERSION="20120404" COMMENT="XMLDB file for Moodle marking guide"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="gradingform_guide_criteria" COMMENT="Stores the rows of the criteria grid.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="definitionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The ID of the form definition this criterion is part of"/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Defines the order of the criterion in the guide"/>
<FIELD NAME="shortname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="shortname of this criterion"/>
<FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The criterion description for students"/>
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="2" NOTNULL="false" SEQUENCE="false" COMMENT="The format of the description field"/>
<FIELD NAME="descriptionmarkers" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Description for Markers"/>
<FIELD NAME="descriptionmarkersformat" TYPE="int" LENGTH="2" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="maxscore" TYPE="number" LENGTH="10" NOTNULL="true" SEQUENCE="false" DECIMALS="5" COMMENT="maximum grade that can be assigned using this criterion"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="fk_definitionid" TYPE="foreign" FIELDS="definitionid" REFTABLE="grading_definitions" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="gradingform_guide_fillings" COMMENT="Stores the data of how the guide is filled by a particular rater">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="instanceid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The ID of the grading form instance"/>
<FIELD NAME="criterionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The ID of the criterion (row) in the guide"/>
<FIELD NAME="remark" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Side note feedback regarding this particular criterion"/>
<FIELD NAME="remarkformat" TYPE="int" LENGTH="2" NOTNULL="false" SEQUENCE="false" COMMENT="The format of the remark field"/>
<FIELD NAME="score" TYPE="number" LENGTH="10" NOTNULL="true" SEQUENCE="false" DECIMALS="5" COMMENT="The score assigned"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="fk_instanceid" TYPE="foreign" FIELDS="instanceid" REFTABLE="grading_instances" REFFIELDS="id"/>
<KEY NAME="fk_criterionid" TYPE="foreign" FIELDS="criterionid" REFTABLE="gradingform_guide_criteria" REFFIELDS="id"/>
<KEY NAME="uq_instance_criterion" TYPE="unique" FIELDS="instanceid, criterionid"/>
</KEYS>
</TABLE>
<TABLE NAME="gradingform_guide_comments" COMMENT="frequently used comments used in marking guide">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="definitionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The ID of the form definition this faq is part of"/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Defines the order of the comments"/>
<FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The comment description"/>
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="2" NOTNULL="false" SEQUENCE="false" COMMENT="The format of the description field"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="fk_definitionid" TYPE="foreign" FIELDS="definitionid" REFTABLE="grading_definitions" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
+41
View File
@@ -0,0 +1,41 @@
<?php
// 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/>.
/**
* External functions and service definitions for the Marking Guide advanced grading form.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$functions = [
'gradingform_guide_grader_gradingpanel_fetch' => [
'classname' => 'gradingform_guide\\grades\\grader\\gradingpanel\\external\\fetch',
'description' => 'Fetch the data required to display the grader grading panel, ' .
'creating the grade item if required',
'type' => 'write',
'ajax' => true,
],
'gradingform_guide_grader_gradingpanel_store' => [
'classname' => 'gradingform_guide\\grades\\grader\\gradingpanel\\external\\store',
'description' => 'Store the grading data for a user from the grader grading panel.',
'type' => 'write',
'ajax' => true,
],
];
+49
View File
@@ -0,0 +1,49 @@
<?php
// 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/>.
/**
* This file keeps track of upgrades to the marking guide grading method.
*
* @package gradingform_guide
* @category upgrade
* @copyright 2016 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Marking guide grading method upgrade task.
*
* @param int $oldversion The version we are upgrading form.
* @return bool Returns true on success.
* @throws coding_exception
* @throws downgrade_exception
* @throws upgrade_exception
*/
function xmldb_gradingform_guide_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+66
View File
@@ -0,0 +1,66 @@
<?php
// 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/>.
/**
* Rubric editor page
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__.'/../../../../config.php');
require_once(__DIR__.'/lib.php');
require_once(__DIR__.'/edit_form.php');
require_once($CFG->dirroot.'/grade/grading/lib.php');
$areaid = required_param('areaid', PARAM_INT);
$manager = get_grading_manager($areaid);
list($context, $course, $cm) = get_context_info_array($manager->get_context()->id);
require_login($course, true, $cm);
require_capability('moodle/grade:managegradingforms', $context);
$controller = $manager->get_controller('guide');
$PAGE->set_url(new moodle_url('/grade/grading/form/guide/edit.php', array('areaid' => $areaid)));
$PAGE->set_title(get_string('definemarkingguide', 'gradingform_guide'));
$PAGE->set_heading(get_string('definemarkingguide', 'gradingform_guide'));
$mform = new gradingform_guide_editguide(null, array('areaid' => $areaid, 'context' => $context,
'allowdraft' => !$controller->has_active_instances()), 'post', '', array('class' => 'gradingform_guide_editform'));
$data = $controller->get_definition_for_editing(true);
$returnurl = optional_param('returnurl', $manager->get_management_url(), PARAM_LOCALURL);
$data->returnurl = $returnurl;
$mform->set_data($data);
if ($mform->is_cancelled()) {
redirect($returnurl);
} else if ($mform->is_submitted() && $mform->is_validated() && !$mform->need_confirm_regrading($controller)) {
// Everything ok, validated, re-grading confirmed if needed. Make changes to the rubric.
$controller->update_definition($mform->get_data());
redirect($returnurl);
}
// Try to keep the session alive on this page as it may take some time
// before significant interaction happens with the server.
\core\session\manager::keepalive();
echo $OUTPUT->header();
$mform->display();
echo $OUTPUT->footer();
+223
View File
@@ -0,0 +1,223 @@
<?php
// 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/>.
/**
* The form used at the guide editor page is defined here
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/lib/formslib.php');
require_once(__DIR__.'/guideeditor.php');
MoodleQuickForm::registerElementType('guideeditor', $CFG->dirroot.'/grade/grading/form/guide/guideeditor.php',
'moodlequickform_guideeditor');
/**
* Defines the guide edit form
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradingform_guide_editguide extends moodleform {
/**
* Form element definition
*/
public function definition() {
$form = $this->_form;
$form->addElement('hidden', 'areaid');
$form->setType('areaid', PARAM_INT);
$form->addElement('hidden', 'returnurl');
$form->setType('returnurl', PARAM_LOCALURL);
// Name.
$form->addElement('text', 'name', get_string('name', 'gradingform_guide'),
array('size' => 52, 'maxlength' => 255));
$form->addRule('name', get_string('required'), 'required', null, 'client');
$form->setType('name', PARAM_TEXT);
$form->addRule('name', null, 'maxlength', 255, 'client');
// Description.
$options = gradingform_guide_controller::description_form_field_options($this->_customdata['context']);
$form->addElement('editor', 'description_editor', get_string('description'), null, $options);
$form->setType('description_editor', PARAM_RAW);
// Guide completion status.
$choices = array();
$choices[gradingform_controller::DEFINITION_STATUS_DRAFT] = html_writer::tag('span',
get_string('statusdraft', 'core_grading'), array('class' => 'status draft'));
$choices[gradingform_controller::DEFINITION_STATUS_READY] = html_writer::tag('span',
get_string('statusready', 'core_grading'), array('class' => 'status ready'));
$form->addElement('select', 'status', get_string('guidestatus', 'gradingform_guide'), $choices)->freeze();
// Guide editor.
$element = $form->addElement('guideeditor', 'guide', get_string('pluginname', 'gradingform_guide'));
$form->setType('guide', PARAM_RAW);
$buttonarray = array();
$buttonarray[] = &$form->createElement('submit', 'saveguide', get_string('saveguide', 'gradingform_guide'));
if ($this->_customdata['allowdraft']) {
$buttonarray[] = &$form->createElement('submit', 'saveguidedraft', get_string('saveguidedraft', 'gradingform_guide'));
}
$editbutton = &$form->createElement('submit', 'editguide', ' ');
$editbutton->freeze();
$buttonarray[] = &$editbutton;
$buttonarray[] = &$form->createElement('cancel');
$form->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$form->closeHeaderBefore('buttonar');
}
/**
* Setup the form depending on current values. This method is called after definition(),
* data submission and set_data().
* All form setup that is dependent on form values should go in here.
*
* We remove the element status if there is no current status (i.e. guide is only being created)
* so the users do not get confused
*/
public function definition_after_data() {
$form = $this->_form;
$el = $form->getElement('status');
if (!$el->getValue()) {
$form->removeElement('status');
} else {
$vals = array_values($el->getValue());
if ($vals[0] == gradingform_controller::DEFINITION_STATUS_READY) {
$this->findbutton('saveguide')->setValue(get_string('save', 'gradingform_guide'));
}
}
}
/**
* Form vlidation.
* If there are errors return array of errors ("fieldname"=>"error message"),
* otherwise true if ok.
*
* @param array $data array of ("fieldname"=>value) of submitted data
* @param array $files array of uploaded files "element_name"=>tmp_file_path
* @return array of "element_name"=>"error_description" if there are errors,
* or an empty array if everything is OK (true allowed for backwards compatibility too).
*/
public function validation($data, $files) {
$err = parent::validation($data, $files);
$err = array();
$form = $this->_form;
$guideel = $form->getElement('guide');
if ($guideel->non_js_button_pressed($data['guide'])) {
// If JS is disabled and button such as 'Add criterion' is pressed - prevent from submit.
$err['guidedummy'] = 1;
} else if (isset($data['editguide'])) {
// Continue editing.
$err['guidedummy'] = 1;
} else if ((isset($data['saveguide']) && $data['saveguide']) ||
(isset($data['saveguidedraft']) && $data['saveguidedraft'])) {
// If user attempts to make guide active - it needs to be validated.
if ($guideel->validate($data['guide']) !== false) {
$err['guidedummy'] = 1;
}
}
return $err;
}
/**
* Return submitted data if properly submitted or returns NULL if validation fails or
* if there is no submitted data.
*
* @return object submitted data; NULL if not valid or not submitted or cancelled
*/
public function get_data() {
$data = parent::get_data();
if (!empty($data->saveguide)) {
$data->status = gradingform_controller::DEFINITION_STATUS_READY;
} else if (!empty($data->saveguidedraft)) {
$data->status = gradingform_controller::DEFINITION_STATUS_DRAFT;
}
return $data;
}
/**
* Check if there are changes in the guide and it is needed to ask user whether to
* mark the current grades for re-grading. User may confirm re-grading and continue,
* return to editing or cancel the changes
*
* @param gradingform_guide_controller $controller
*/
public function need_confirm_regrading($controller) {
$data = $this->get_data();
if (isset($data->guide['regrade'])) {
// We have already displayed the confirmation on the previous step.
return false;
}
if (!isset($data->saveguide) || !$data->saveguide) {
// We only need confirmation when button 'Save guide' is pressed.
return false;
}
if (!$controller->has_active_instances()) {
// Nothing to re-grade, confirmation not needed.
return false;
}
$changelevel = $controller->update_or_check_guide($data);
if ($changelevel == 0) {
// No changes in the guide, no confirmation needed.
return false;
}
// Freeze form elements and pass the values in hidden fields.
// TODO description_editor does not freeze the normal way!
$form = $this->_form;
foreach (array('guide', 'name'/*, 'description_editor'*/) as $fieldname) {
$el =& $form->getElement($fieldname);
$el->freeze();
$el->setPersistantFreeze(true);
if ($fieldname == 'guide') {
$el->add_regrade_confirmation($changelevel);
}
}
// Replace button text 'saveguide' and unfreeze 'Back to edit' button.
$this->findbutton('saveguide')->setValue(get_string('continue'));
$el =& $this->findbutton('editguide');
$el->setValue(get_string('backtoediting', 'gradingform_guide'));
$el->unfreeze();
return true;
}
/**
* Returns a form element (submit button) with the name $elementname
*
* @param string $elementname
* @return HTML_QuickForm_element
*/
protected function &findbutton($elementname) {
$form = $this->_form;
$buttonar =& $form->getElement('buttonar');
$elements =& $buttonar->getElements();
foreach ($elements as $el) {
if ($el->getName() == $elementname) {
return $el;
}
}
return null;
}
}
+374
View File
@@ -0,0 +1,374 @@
<?php
// 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/>.
/**
* This file contains the marking guide editor element
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once("HTML/QuickForm/input.php");
/**
* The editor for the marking guide advanced grading plugin.
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlequickform_guideeditor extends HTML_QuickForm_input {
/** @var string help message */
public $_helpbutton = '';
/** @var null|false|string stores the result of the last validation: null - undefined, false - no errors,
* string - error(s) text */
protected $validationerrors = null;
/** @var bool if element has already been validated **/
protected $wasvalidated = false;
/** @var null|bool If non-submit (JS) button was pressed: null - unknown, true/false - button was/wasn't pressed */
protected $nonjsbuttonpressed = false;
/** @var string|false Message to display in front of the editor (that there exist grades on this guide being edited) */
protected $regradeconfirmation = false;
/**
* Constructor
*
* @param string $elementname
* @param string $elementlabel
* @param array $attributes
*/
public function __construct($elementname=null, $elementlabel=null, $attributes=null) {
parent::__construct($elementname, $elementlabel, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function moodlequickform_guideeditor($elementname=null, $elementlabel=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementname, $elementlabel, $attributes);
}
/**
* get html for help button
*
* @return string html for help button
*/
public function getHelpButton() {
return $this->_helpbutton;
}
/**
* The renderer will take care itself about different display in normal and frozen states
*
* @return string
*/
public function getElementTemplateType() {
return 'default';
}
/**
* Specifies that confirmation about re-grading needs to be added to this rubric editor.
* $changelevel is saved in $this->regradeconfirmation and retrieved in toHtml()
*
* @see gradingform_rubric_controller::update_or_check_rubric()
* @param int $changelevel
*/
public function add_regrade_confirmation($changelevel) {
$this->regradeconfirmation = $changelevel;
}
/**
* Returns html string to display this element
*
* @return string
*/
public function toHtml() {
global $PAGE;
$html = $this->_getTabs();
$renderer = $PAGE->get_renderer('gradingform_guide');
$data = $this->prepare_data(null, $this->wasvalidated);
if (!$this->_flagFrozen) {
$mode = gradingform_guide_controller::DISPLAY_EDIT_FULL;
$module = array('name'=>'gradingform_guideeditor',
'fullpath'=>'/grade/grading/form/guide/js/guideeditor.js',
'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
'strings' => array(
array('confirmdeletecriterion', 'gradingform_guide'),
array('clicktoedit', 'gradingform_guide'),
array('clicktoeditname', 'gradingform_guide')
));
$PAGE->requires->js_init_call('M.gradingform_guideeditor.init', array(
array('name' => $this->getName(),
'criteriontemplate' => $renderer->criterion_template($mode, $data['options'], $this->getName()),
'commenttemplate' => $renderer->comment_template($mode, $this->getName())
)),
true, $module);
} else {
// Guide is frozen, no javascript needed.
if ($this->_persistantFreeze) {
$mode = gradingform_guide_controller::DISPLAY_EDIT_FROZEN;
} else {
$mode = gradingform_guide_controller::DISPLAY_PREVIEW;
}
}
if ($this->regradeconfirmation) {
if (!isset($data['regrade'])) {
$data['regrade'] = 1;
}
$html .= $renderer->display_regrade_confirmation($this->getName(), $this->regradeconfirmation, $data['regrade']);
}
if ($this->validationerrors) {
$html .= html_writer::div($renderer->notification($this->validationerrors));
}
$html .= $renderer->display_guide($data['criteria'], $data['comments'], $data['options'], $mode, $this->getName());
return $html;
}
/**
* Prepares the data passed in $_POST:
* - processes the pressed buttons 'addlevel', 'addcriterion', 'moveup', 'movedown', 'delete' (when JavaScript is disabled)
* sets $this->nonjsbuttonpressed to true/false if such button was pressed
* - if options not passed (i.e. we create a new guide) fills the options array with the default values
* - if options are passed completes the options array with unchecked checkboxes
* - if $withvalidation is set, adds 'error_xxx' attributes to elements that contain errors and creates an error string
* and stores it in $this->validationerrors
*
* @param array $value
* @param boolean $withvalidation whether to enable data validation
* @return array
*/
protected function prepare_data($value = null, $withvalidation = false) {
if (null === $value) {
$value = $this->getValue();
}
if ($this->nonjsbuttonpressed === null) {
$this->nonjsbuttonpressed = false;
}
$errors = array();
$return = array('criteria' => array(), 'options' => gradingform_guide_controller::get_default_options(),
'comments' => array());
if (!isset($value['criteria'])) {
$value['criteria'] = array();
$errors['err_nocriteria'] = 1;
}
// If options are present in $value, replace default values with submitted values.
if (!empty($value['options'])) {
foreach (array_keys($return['options']) as $option) {
// Special treatment for checkboxes.
if (!empty($value['options'][$option])) {
$return['options'][$option] = $value['options'][$option];
} else {
$return['options'][$option] = null;
}
}
}
if (is_array($value)) {
// For other array keys of $value no special treatmeant neeeded, copy them to return value as is.
foreach (array_keys($value) as $key) {
if ($key != 'options' && $key != 'criteria' && $key != 'comments') {
$return[$key] = $value[$key];
}
}
}
// Iterate through criteria.
$lastaction = null;
$lastid = null;
foreach ($value['criteria'] as $id => $criterion) {
if ($id == 'addcriterion') {
$id = $this->get_next_id(array_keys($value['criteria']));
$criterion = array('description' => '');
$this->nonjsbuttonpressed = true;
}
if ($withvalidation && !array_key_exists('delete', $criterion)) {
if (!strlen(trim($criterion['shortname']))) {
$errors['err_noshortname'] = 1;
$criterion['error_description'] = true;
}
if (strlen(trim($criterion['shortname'])) > 255) {
$errors['err_shortnametoolong'] = 1;
$criterion['error_description'] = true;
}
if (!strlen(trim($criterion['maxscore']))) {
$errors['err_nomaxscore'] = 1;
$criterion['error_description'] = true;
} else if (!is_numeric($criterion['maxscore'])) {
$errors['err_maxscorenotnumeric'] = 1;
$criterion['error_description'] = true;
} else if ($criterion['maxscore'] < 0) {
$errors['err_maxscoreisnegative'] = 1;
$criterion['error_description'] = true;
}
}
if (array_key_exists('moveup', $criterion) || $lastaction == 'movedown') {
unset($criterion['moveup']);
if ($lastid !== null) {
$lastcriterion = $return['criteria'][$lastid];
unset($return['criteria'][$lastid]);
$return['criteria'][$id] = $criterion;
$return['criteria'][$lastid] = $lastcriterion;
} else {
$return['criteria'][$id] = $criterion;
}
$lastaction = null;
$lastid = $id;
$this->nonjsbuttonpressed = true;
} else if (array_key_exists('delete', $criterion)) {
$this->nonjsbuttonpressed = true;
} else {
if (array_key_exists('movedown', $criterion)) {
unset($criterion['movedown']);
$lastaction = 'movedown';
$this->nonjsbuttonpressed = true;
}
$return['criteria'][$id] = $criterion;
$lastid = $id;
}
}
// Add sort order field to criteria.
$csortorder = 1;
foreach (array_keys($return['criteria']) as $id) {
$return['criteria'][$id]['sortorder'] = $csortorder++;
}
// Iterate through comments.
$lastaction = null;
$lastid = null;
if (!empty($value['comments'])) {
foreach ($value['comments'] as $id => $comment) {
if ($id == 'addcomment') {
$id = $this->get_next_id(array_keys($value['comments']));
$comment = array('description' => '');
$this->nonjsbuttonpressed = true;
}
if (array_key_exists('moveup', $comment) || $lastaction == 'movedown') {
unset($comment['moveup']);
if ($lastid !== null) {
$lastcomment = $return['comments'][$lastid];
unset($return['comments'][$lastid]);
$return['comments'][$id] = $comment;
$return['comments'][$lastid] = $lastcomment;
} else {
$return['comments'][$id] = $comment;
}
$lastaction = null;
$lastid = $id;
$this->nonjsbuttonpressed = true;
} else if (array_key_exists('delete', $comment)) {
$this->nonjsbuttonpressed = true;
} else {
if (array_key_exists('movedown', $comment)) {
unset($comment['movedown']);
$lastaction = 'movedown';
$this->nonjsbuttonpressed = true;
}
$return['comments'][$id] = $comment;
$lastid = $id;
}
}
// Add sort order field to comments.
$csortorder = 1;
foreach (array_keys($return['comments']) as $id) {
$return['comments'][$id]['sortorder'] = $csortorder++;
}
}
// Create validation error string (if needed).
if ($withvalidation) {
if (count($errors)) {
$rv = array();
foreach ($errors as $error => $v) {
$rv[] = get_string($error, 'gradingform_guide');
}
$this->validationerrors = join('<br/ >', $rv);
} else {
$this->validationerrors = false;
}
$this->wasvalidated = true;
}
return $return;
}
/**
* Scans array $ids to find the biggest element ! NEWID*, increments it by 1 and returns
*
* @param array $ids
* @return string
*/
protected function get_next_id($ids) {
$maxid = 0;
foreach ($ids as $id) {
if (preg_match('/^NEWID(\d+)$/', $id, $matches) && ((int)$matches[1]) > $maxid) {
$maxid = (int)$matches[1];
}
}
return 'NEWID'.($maxid+1);
}
/**
* Checks if a submit button was pressed which is supposed to be processed on client side by JS
* but user seem to have disabled JS in the browser.
* (buttons 'add criteria', 'add level', 'move up', 'move down', 'add comment')
* In this case the form containing this element is prevented from being submitted
*
* @param array $value
* @return boolean true if non-submit button was pressed and not processed by JS
*/
public function non_js_button_pressed($value) {
if ($this->nonjsbuttonpressed === null) {
$this->prepare_data($value);
}
return $this->nonjsbuttonpressed;
}
/**
* Validates that guide has at least one criterion, filled definitions and all criteria
* have filled descriptions
*
* @param array $value
* @return string|false error text or false if no errors found
*/
public function validate($value) {
if (!$this->wasvalidated) {
$this->prepare_data($value, true);
}
return $this->validationerrors;
}
/**
* Prepares the data for saving
* @see prepare_data()
*
* @param array $submitvalues
* @param boolean $assoc
* @return array
*/
public function exportValue(&$submitvalues, $assoc = false) {
$value = $this->prepare_data($this->_findValue($submitvalues));
return $this->_prepareValue($value, $assoc);
}
}
+32
View File
@@ -0,0 +1,32 @@
M.gradingform_guide = {};
/**
* This function is called for each guide on page.
*/
M.gradingform_guide.init = function(Y, options) {
var currentfocus = Y.one('.markingguideremark');
Y.all('.markingguideremark').on('blur', function(e) {
currentfocus = e.currentTarget;
});
Y.all('.markingguidecomment').on('click', function(e) {
currentfocus.set('value', currentfocus.get('value') + '\n' + e.currentTarget.get('text'));
currentfocus.focus();
});
Y.all('.showmarkerdesc input[type=radio]').on('click', function(e) {
if (e.currentTarget.get('value')=='false') {
Y.all('.criteriondescriptionmarkers').addClass('hide');
} else {
Y.all('.criteriondescriptionmarkers').removeClass('hide');
}
});
Y.all('.showstudentdesc input[type=radio]').on('click', function(e) {
if (e.currentTarget.get('value')=='false') {
Y.all('.criteriondescription').addClass('hide');
} else {
Y.all('.criteriondescription').removeClass('hide');
}
});
};
+267
View File
@@ -0,0 +1,267 @@
M.gradingform_guideeditor = {'templates' : {}, 'eventhandler' : null, 'name' : null, 'Y' : null};
/**
* This function is called for each guideeditor on page.
*/
M.gradingform_guideeditor.init = function(Y, options) {
M.gradingform_guideeditor.name = options.name
M.gradingform_guideeditor.Y = Y
M.gradingform_guideeditor.templates[options.name] = {
'criterion' : options.criteriontemplate,
'comment' : options.commenttemplate
}
M.gradingform_guideeditor.disablealleditors()
Y.on('click', M.gradingform_guideeditor.clickanywhere, 'body', null)
YUI().use('event-touch', function (Y) {
Y.one('body').on('touchstart', M.gradingform_guideeditor.clickanywhere);
Y.one('body').on('touchend', M.gradingform_guideeditor.clickanywhere);
})
M.gradingform_guideeditor.addhandlers()
};
// Adds handlers for clicking submit button. This function must be called each time JS adds new elements to html
M.gradingform_guideeditor.addhandlers = function() {
var Y = M.gradingform_guideeditor.Y
var name = M.gradingform_guideeditor.name
if (M.gradingform_guideeditor.eventhandler) {
M.gradingform_guideeditor.eventhandler.detach()
}
M.gradingform_guideeditor.eventhandler = Y.on('click', M.gradingform_guideeditor.buttonclick, '#guide-'+name+' input[type=submit]', null);
}
// switches all input text elements to non-edit mode
M.gradingform_guideeditor.disablealleditors = function() {
var Y = M.gradingform_guideeditor.Y
var name = M.gradingform_guideeditor.name
Y.all('#guide-'+name+' .criteria .description input[type=text]:not(.pseudotablink)').each( function(node) {M.gradingform_guideeditor.editmode(node, false)} );
Y.all('#guide-'+name+' .criteria .description textarea').each( function(node) {M.gradingform_guideeditor.editmode(node, false)} );
Y.all('#guide-'+name+' .comments .description textarea').each( function(node) {M.gradingform_guideeditor.editmode(node, false)} );
}
// function invoked on each click on the page. If criterion values are clicked
// it switches the element to edit mode. If guide button is clicked it does nothing so the 'buttonclick'
// function is invoked
M.gradingform_guideeditor.clickanywhere = function(e) {
if (e.type == 'touchstart') {
return
}
var el = e.target
// if clicked on button - disablecurrenteditor, continue
if (el.get('tagName') == 'INPUT' && el.get('type') == 'submit') {
return
}
// if clicked on description item and this item is not enabled - enable it
var container = null
if ((container = el.ancestor('.criterionname')) || (container = el.ancestor('.criterionmaxscore'))) {
el = container.one('input[type=text]')
} else if ((container = el.ancestor('.criteriondesc')) || (container = el.ancestor('.criteriondescmarkers'))) {
el = container.one('textarea')
} else {
el = null
}
if (el) {
if (el.hasClass('hiddenelement')) {
M.gradingform_guideeditor.disablealleditors()
M.gradingform_guideeditor.editmode(el, true)
}
return
}
// else disablecurrenteditor
M.gradingform_guideeditor.disablealleditors()
}
// switch the criterion item to edit mode or switch back
M.gradingform_guideeditor.editmode = function(el, editmode) {
var Y = M.gradingform_guideeditor.Y
var ta = el
if (!editmode && ta.hasClass('hiddenelement')) {
return;
}
if (editmode && !ta.hasClass('hiddenelement')) {
return;
}
var pseudotablink = '<span class="pseudotablink" tabindex="0"></span>',
taplain = ta.next('.plainvalue'),
tbplain = null,
tb = el.one('.score input[type=text]')
// add 'plainvalue' next to textarea for description/definition and next to input text field for score (if applicable)
if (!taplain && ta.get('name') != '') {
ta.insert('<div class="plainvalue">'+pseudotablink+'<span class="textvalue">&nbsp;</span></div>', 'after')
taplain = ta.next('.plainvalue')
taplain.one('.pseudotablink').on('focus', M.gradingform_guideeditor.clickanywhere)
if (tb) {
tb.get('parentNode').append('<span class="plainvalue">'+pseudotablink+'<span class="textvalue">&nbsp;</span></span>')
tbplain = tb.get('parentNode').one('.plainvalue')
tbplain.one('.pseudotablink').on('focus', M.gradingform_guideeditor.clickanywhere)
}
}
if (tb && !tbplain) {
tbplain = tb.get('parentNode').one('.plainvalue')
}
if (!editmode) {
// if we need to hide the input fields, copy their contents to plainvalue(s). If description/definition
// is empty, display the default text ('Click to edit ...') and add/remove 'empty' CSS class to element
var value = Y.Lang.trim(ta.get('value'));
if (value.length) {
taplain.removeClass('empty')
} else if (ta.get('name').indexOf('[shortname]') > 1){
value = M.util.get_string('clicktoeditname', 'gradingform_guide')
taplain.addClass('editname')
} else {
value = M.util.get_string('clicktoedit', 'gradingform_guide')
taplain.addClass('empty')
}
// Replace newlines with <br> tags, when displaying in the page.
taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value).replace(/(?:\r\n|\r|\n)/g, '<br>'))
if (tb) {
tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')))
}
// hide/display textarea, textbox and plaintexts
taplain.removeClass('hiddenelement')
ta.addClass('hiddenelement')
if (tb) {
tbplain.removeClass('hiddenelement')
tb.addClass('hiddenelement')
}
} else {
// if we need to show the input fields, set the width/height for textarea so it fills the cell
try {
if (ta.get('name').indexOf('[maxscore]') > 1) {
ta.setStyle('width', '25px');
} else {
var width = parseFloat(ta.get('parentNode').getComputedStyle('width'))-10,
height = parseFloat(ta.get('parentNode').getComputedStyle('height'))
ta.setStyle('width', Math.max(width,50)+'px')
ta.setStyle('height', Math.max(height,30)+'px')
}
}
catch (err) {
// this browser do not support 'computedStyle', leave the default size of the textbox
}
// hide/display textarea, textbox and plaintexts
taplain.addClass('hiddenelement')
ta.removeClass('hiddenelement')
if (tb) {
tbplain.addClass('hiddenelement')
tb.removeClass('hiddenelement')
}
}
// focus the proper input field in edit mode
if (editmode) {
ta.focus()
}
}
// handler for clicking on submit buttons within guideeditor element. Adds/deletes/rearranges criteria/comments on client side
M.gradingform_guideeditor.buttonclick = function(e, confirmed) {
var Y = M.gradingform_guideeditor.Y
var name = M.gradingform_guideeditor.name
if (e.target.get('type') != 'submit') {
return;
}
M.gradingform_guideeditor.disablealleditors()
var chunks = e.target.get('id').split('-')
var section = chunks[1]
var action = chunks[chunks.length-1]
if (chunks[0] != name || (section != 'criteria' && section != 'comments')) {
return;
}
// prepare the id of the next inserted criterion
var elements_str;
if (section == 'criteria') {
elements_str = '#guide-'+name+' .criteria .criterion'
} else if (section == 'comments') {
elements_str = '#guide-'+name+' .comments .criterion'
}
var newid = 0;
if (action == 'addcriterion' || action == 'addcomment') {
newid = M.gradingform_guideeditor.calculatenewid(elements_str);
}
if (chunks.length == 3 && (action == 'addcriterion' || action == 'addcomment')) {
// ADD NEW CRITERION OR COMMENT
var parentel = Y.one('#'+name+'-'+section)
if (parentel.one('>tbody')) {
parentel = parentel.one('>tbody')
}
if (section == 'criteria') {
var newcriterion = M.gradingform_guideeditor.templates[name]['criterion']
parentel.append(newcriterion.replace(/\{CRITERION-id\}/g, 'NEWID'+newid).replace(/\{.+?\}/g, ''))
} else if (section == 'comments') {
var newcomment = M.gradingform_guideeditor.templates[name]['comment']
parentel.append(newcomment.replace(/\{COMMENT-id\}/g, 'NEWID'+newid).replace(/\{.+?\}/g, ''))
}
M.gradingform_guideeditor.addhandlers();
M.gradingform_guideeditor.disablealleditors()
M.gradingform_guideeditor.assignclasses(elements_str)
// Enable edit mode of the newly added criterion/comment entry.
var inputTarget = 'shortname';
if (action == 'addcomment') {
inputTarget = 'description';
}
var inputTargetId = '#guide-' + name + ' #' + name + '-' + section + '-NEWID' + newid + '-' + inputTarget;
M.gradingform_guideeditor.editmode(Y.one(inputTargetId), true);
} else if (chunks.length == 4 && action == 'moveup') {
// MOVE UP
el = Y.one('#'+name+'-'+section+'-'+chunks[2])
if (el.previous()) {
el.get('parentNode').insertBefore(el, el.previous())
}
M.gradingform_guideeditor.assignclasses(elements_str)
} else if (chunks.length == 4 && action == 'movedown') {
// MOVE DOWN
el = Y.one('#'+name+'-'+section+'-'+chunks[2])
if (el.next()) {
el.get('parentNode').insertBefore(el.next(), el)
}
M.gradingform_guideeditor.assignclasses(elements_str)
} else if (chunks.length == 4 && action == 'delete') {
// DELETE
if (confirmed) {
Y.one('#'+name+'-'+section+'-'+chunks[2]).remove()
M.gradingform_guideeditor.assignclasses(elements_str)
} else {
require(['core/notification', 'core/str'], function(Notification, Str) {
Notification.saveCancelPromise(
Str.get_string('confirmation', 'admin'),
Str.get_string('confirmdeletecriterion', 'gradingform_guide'),
Str.get_string('yes', 'moodle')
).then(function() {
M.gradingform_guideeditor.buttonclick.apply(this, [e, true]);
return;
}.bind(this)).catch(function() {
// User cancelled.
});
}.bind(this));
}
} else {
// unknown action
return;
}
e.preventDefault();
}
// properly set classes (first/last/odd/even) and/or criterion sortorder for elements Y.all(elements_str)
M.gradingform_guideeditor.assignclasses = function (elements_str) {
var elements = M.gradingform_guideeditor.Y.all(elements_str)
for (var i=0; i<elements.size(); i++) {
elements.item(i).removeClass('first').removeClass('last').removeClass('even').removeClass('odd').
addClass(((i%2)?'odd':'even') + ((i==0)?' first':'') + ((i==elements.size()-1)?' last':''))
elements.item(i).all('input[type=hidden]').each(
function(node) {if (node.get('name').match(/sortorder/)) { node.set('value', i)}}
);
}
}
// returns unique id for the next added element, it should not be equal to any of Y.all(elements_str) ids
M.gradingform_guideeditor.calculatenewid = function (elements_str) {
var newid = 1
M.gradingform_guideeditor.Y.all(elements_str).each( function(node) {
var idchunks = node.get('id').split('-'), id = idchunks.pop();
if (id.match(/^NEWID(\d+)$/)) { newid = Math.max(newid, parseInt(id.substring(5))+1)};
} );
return newid
}
@@ -0,0 +1,104 @@
<?php
// 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/>.
/**
* Strings for the marking guide advanced grading plugin
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['addcomment'] = 'Add frequently used comment';
$string['additionalcomments'] = 'Additional comments';
$string['additionalcommentsforcriterion'] = 'Additional comments for criterion, {$a}';
$string['addcriterion'] = 'Add criterion';
$string['alwaysshowdefinition'] = 'Show guide definition to students';
$string['backtoediting'] = 'Back to editing';
$string['clicktocopy'] = 'Click to copy this text into the criteria feedback';
$string['clicktoedit'] = 'Click to edit';
$string['clicktoeditname'] = 'Click to edit criterion name';
$string['comment'] = 'Comment';
$string['commentpickerforcriterion'] = 'Frequently used comments picker for {$a} additional comments';
$string['comments'] = 'Frequently used comments';
$string['commentsdelete'] = 'Delete comment';
$string['commentsempty'] = 'Click to edit comment';
$string['commentsmovedown'] = 'Move down';
$string['commentsmoveup'] = 'Move up';
$string['confirmdeletecriterion'] = 'Are you sure you want to delete this item?';
$string['confirmdeletelevel'] = 'Are you sure you want to delete this level?';
$string['criterion'] = 'Criterion name';
$string['criteriondelete'] = 'Delete criterion';
$string['criterionempty'] = 'Click to edit criterion';
$string['criterionmovedown'] = 'Move down';
$string['criterionmoveup'] = 'Move up';
$string['criterionname'] = 'Criterion name';
$string['criterionremark'] = '{$a} criterion remark';
$string['definemarkingguide'] = 'Define marking guide';
$string['description'] = 'Description';
$string['descriptionmarkers'] = 'Description for Markers';
$string['descriptionstudents'] = 'Description for students';
$string['err_maxscoreisnegative'] = 'The max score is not valid, negative values are not allowed';
$string['err_maxscorenotnumeric'] = 'Criterion max score must be numeric';
$string['err_nocomment'] = 'Comment can not be empty';
$string['err_nodescription'] = 'Student description can not be empty';
$string['err_nodescriptionmarkers'] = 'Marker description can not be empty';
$string['err_nomaxscore'] = 'Criterion max score can not be empty';
$string['err_noshortname'] = 'Criterion name can not be empty';
$string['err_shortnametoolong'] = 'Criterion name must be less than 256 characters';
$string['err_scoreinvalid'] = 'The score given to \'{$a->criterianame}\' is not valid, the max score is: {$a->maxscore}';
$string['err_scoreisnegative'] = 'The score given to \'{$a->criterianame}\' is not valid, negative values are not allowed';
$string['gradingof'] = '{$a} grading';
$string['guide'] = 'Marking guide';
$string['guidemappingexplained'] = 'WARNING: Your marking guide has a maximum grade of <b>{$a->maxscore} points</b> but the maximum grade set in your activity is {$a->modulegrade} The maximum score set in your marking guide will be scaled to the maximum grade in the module.<br />
Intermediate scores will be converted respectively and rounded to the nearest available grade.';
$string['guidenotcompleted'] = 'Please provide a valid grade for each criterion';
$string['guideoptions'] = 'Marking guide options';
$string['guidestatus'] = 'Current marking guide status';
$string['hidemarkerdesc'] = 'Hide marker criterion descriptions';
$string['hidestudentdesc'] = 'Hide student criterion descriptions';
$string['informationforcriterion'] = '{$a} information';
$string['insertcomment'] = 'Insert frequently used comment';
$string['maxscore'] = 'Maximum score';
$string['name'] = 'Name';
$string['needregrademessage'] = 'The marking guide definition was changed after this student had been graded. The student can not see this marking guide until you check the marking guide and update the grade.';
$string['outof'] = 'Score out of {$a}';
$string['pluginname'] = 'Marking guide';
$string['previewmarkingguide'] = 'Preview marking guide';
$string['privacy:metadata:criterionid'] = 'An identifier to a criterion for advanced marking.';
$string['privacy:metadata:fillingssummary'] = 'Stores information about a user\'s grade and feedback for the marking guide.';
$string['privacy:metadata:instanceid'] = 'An identifier to a grade used by an activity.';
$string['privacy:metadata:preference:showmarkerdesc'] = 'Whether to show marker criterion descriptions';
$string['privacy:metadata:preference:showstudentdesc'] = 'Whether to show student criterion descriptions';
$string['privacy:metadata:remark'] = 'Remarks related to this grade criterion.';
$string['privacy:metadata:score'] = 'A score for this grade criterion.';
$string['regrademessage1'] = 'You are about to save changes to a marking guide that has already been used for grading. Please indicate if existing grades need to be reviewed. If you set this then the marking guide will be hidden from students until their item is regraded.';
$string['regrademessage5'] = 'You are about to save significant changes to a marking guide that has already been used for grading. The gradebook value will be unchanged, but the marking guide will be hidden from students until their item is regraded.';
$string['regradeoption0'] = 'Do not mark for regrade';
$string['regradeoption1'] = 'Mark for regrade';
$string['remark_help'] = 'Enter any additional comments about this criterion.';
$string['restoredfromdraft'] = 'NOTE: The last attempt to grade this person was not saved properly so draft grades have been restored. If you want to cancel these changes use the \'Cancel\' button below.';
$string['save'] = 'Save';
$string['saveguide'] = 'Save marking guide and make it ready';
$string['saveguidedraft'] = 'Save as draft';
$string['score'] = 'score';
$string['scoreforcriterion'] = '{$a} score';
$string['score_help'] = 'Enter a score for {$a->criterion} between 0 and {$a->maxscore}.';
$string['showmarkerdesc'] = 'Show marker criterion descriptions';
$string['showmarkspercriterionstudents'] = 'Show marks per criterion to students';
$string['showstudentdesc'] = 'Show student criterion descriptions';
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M15 0H1C.5 0 0 .5 0 1v14c0 .5.5 1 1 1h14c.5 0 1-.5 1-1V1c0-.5-.5-1-1-1zM8 14H2v-2h6v2zm0-3H2V9h6v2zm0-3H2V6h6v2zm3 6H9v-2h2v2zm0-3H9V9h2v2zm0-3H9V6h2v2zm3 6h-2v-2h2v2zm0-3h-2V9h2v2zm0-3h-2V6h2v2z" fill="#888"/></svg>

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 14c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm1.2-2c0 .5-.5 1-1 1h-.4c-.5 0-1-.5-1-1V7.4c0-.5.5-1 1-1h.5c.5 0 1 .5 1 1V12zm0-7.8c0 .7-.6 1.2-1.2 1.2-.7 0-1.2-.6-1.2-1.2C6.8 3.5 7.3 3 8 3s1.2.5 1.2 1.2z" fill="#888"/></svg>

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M11 4.5H7.5V1c0-.5-.5-1-1-1h-1c-.5 0-1 .5-1 1v3.5H1c-.5 0-1 .5-1 1v1c0 .5.5 1 1 1h3.5V11c0 .5.5 1 1 1h1c.5 0 1-.5 1-1V7.5H11c.6 0 1-.5 1-1v-1c0-.5-.4-1-1-1z" fill="#888"/></svg>

After

Width:  |  Height:  |  Size: 479 B

+53
View File
@@ -0,0 +1,53 @@
<?php
// 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/>.
/**
* Preview marking guide page
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__.'/../../../../config.php');
require_once(__DIR__.'/lib.php');
require_once(__DIR__.'/edit_form.php');
require_once($CFG->dirroot.'/grade/grading/lib.php');
$areaid = required_param('areaid', PARAM_INT);
$manager = get_grading_manager($areaid);
list($context, $course, $cm) = get_context_info_array($manager->get_context()->id);
require_login($course, true, $cm);
$controller = $manager->get_controller('guide');
$options = $controller->get_options();
if (!$controller->is_form_defined() || empty($options['alwaysshowdefinition'])) {
throw new moodle_exception('nopermissions', 'error', '', get_string('previewmarkingguide', 'gradingform_guide'));
}
$title = get_string('gradingof', 'gradingform_guide', $manager->get_area_title());
$PAGE->set_url(new moodle_url('/grade/grading/form/guide/preview.php', array('areaid' => $areaid)));
$PAGE->set_title($title);
$PAGE->set_heading($title);
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
echo $controller->render_preview($PAGE);
echo $OUTPUT->footer();
+793
View File
@@ -0,0 +1,793 @@
<?php
// 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/>.
/**
* Contains the Guide grading form renderer in all of its glory
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Grading method plugin renderer
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradingform_guide_renderer extends plugin_renderer_base {
/**
* This function returns html code for displaying criterion. Depending on $mode it may be the
* code to edit guide, to preview the guide, to evaluate somebody or to review the evaluation.
*
* This function may be called from display_guide() to display the whole guide, or it can be
* called by itself to return a template used by JavaScript to add new empty criteria to the
* guide being designed.
* In this case it will use macros like {NAME}, {LEVELS}, {CRITERION-id}, etc.
*
* When overriding this function it is very important to remember that all elements of html
* form (in edit or evaluate mode) must have the name $elementname.
*
* Also JavaScript relies on the class names of elements and when developer changes them
* script might stop working.
*
* @param int $mode guide display mode, one of gradingform_guide_controller::DISPLAY_* {@link gradingform_guide_controller()}
* @param array $options An array of options.
* showmarkspercriterionstudents (bool) If true adds the current score to the display
* @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
* @param array $criterion criterion data
* @param array $value (only in view mode) teacher's feedback on this criterion
* @param array $validationerrors An array containing validation errors to be shown
* @param array $comments Array of frequently used comments.
* @return string
*/
public function criterion_template($mode, $options, $elementname = '{NAME}', $criterion = null, $value = null,
$validationerrors = null, $comments = null) {
if ($criterion === null || !is_array($criterion) || !array_key_exists('id', $criterion)) {
$criterion = array('id' => '{CRITERION-id}',
'description' => '{CRITERION-description}',
'sortorder' => '{CRITERION-sortorder}',
'class' => '{CRITERION-class}',
'descriptionmarkers' => '{CRITERION-descriptionmarkers}',
'shortname' => '{CRITERION-shortname}',
'maxscore' => '{CRITERION-maxscore}');
} else {
foreach (array('sortorder', 'description', 'class', 'shortname', 'descriptionmarkers', 'maxscore') as $key) {
// Set missing array elements to empty strings to avoid warnings.
if (!array_key_exists($key, $criterion)) {
$criterion[$key] = '';
}
}
}
$criteriontemplate = html_writer::start_tag('tr', array('class' => 'criterion'. $criterion['class'],
'id' => '{NAME}-criteria-{CRITERION-id}'));
$descriptionclass = 'description';
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL) {
$criteriontemplate .= html_writer::start_tag('td', array('class' => 'controls'));
foreach (array('moveup', 'delete', 'movedown') as $key) {
$value = get_string('criterion'.$key, 'gradingform_guide');
$button = html_writer::empty_tag('input', array('type' => 'submit',
'name' => '{NAME}[criteria][{CRITERION-id}]['.$key.']',
'id' => '{NAME}-criteria-{CRITERION-id}-'.$key, 'value' => $value, 'title' => $value));
$criteriontemplate .= html_writer::tag('div', $button, array('class' => $key));
}
$criteriontemplate .= html_writer::end_tag('td'); // Controls.
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
$shortnameinput = html_writer::empty_tag('input', array('type' => 'text',
'name' => '{NAME}[criteria][{CRITERION-id}][shortname]',
'id ' => '{NAME}-criteria-{CRITERION-id}-shortname',
'value' => $criterion['shortname'],
'aria-labelledby' => '{NAME}-criterion-name-label'));
$shortname = html_writer::tag('div', $shortnameinput, array('class' => 'criterionname'));
$descriptioninput = html_writer::tag('textarea', s($criterion['description']),
array('name' => '{NAME}[criteria][{CRITERION-id}][description]',
'id' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '65', 'rows' => '5'));
$description = html_writer::tag('div', $descriptioninput, array('class' => 'criteriondesc'));
$descriptionmarkersinput = html_writer::tag('textarea', s($criterion['descriptionmarkers']),
array('name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]',
'id' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]', 'cols' => '65', 'rows' => '5'));
$descriptionmarkers = html_writer::tag('div', $descriptionmarkersinput, array('class' => 'criteriondescmarkers'));
$maxscore = html_writer::empty_tag('input', array('type'=> 'text',
'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]', 'size' => '3',
'value' => $criterion['maxscore'],
'id' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
$maxscore = html_writer::tag('div', $maxscore, array('class'=>'criterionmaxscore'));
} else {
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FROZEN) {
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[criteria][{CRITERION-id}][shortname]', 'value' => $criterion['shortname']));
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[criteria][{CRITERION-id}][description]', 'value' => $criterion['description']));
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]', 'value' => $criterion['descriptionmarkers']));
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]', 'value' => $criterion['maxscore']));
} else if ($mode == gradingform_guide_controller::DISPLAY_EVAL ||
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
$descriptionclass = 'descriptionreadonly';
}
$shortnameparams = array(
'name' => '{NAME}[criteria][{CRITERION-id}][shortname]',
'id' => '{NAME}[criteria][{CRITERION-id}][shortname]',
'aria-describedby' => '{NAME}-criterion-name-label'
);
$shortname = html_writer::div(
format_text($criterion['shortname'], FORMAT_HTML),
'criterionshortname',
$shortnameparams
);
$descmarkerclass = '';
$descstudentclass = '';
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
if (!get_user_preferences('gradingform_guide-showmarkerdesc', true)) {
$descmarkerclass = ' hide';
}
if (!get_user_preferences('gradingform_guide-showstudentdesc', true)) {
$descstudentclass = ' hide';
}
}
$description = html_writer::tag('div',
format_text($criterion['description'], $criterion['descriptionformat']),
array('class'=>'criteriondescription'.$descstudentclass,
'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
$descriptionmarkers = html_writer::tag('div',
format_text($criterion['descriptionmarkers'], $criterion['descriptionmarkersformat']),
array('class'=>'criteriondescriptionmarkers'.$descmarkerclass,
'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
$maxscore = html_writer::tag('div', s($criterion['maxscore']),
array('class'=>'criteriondescriptionscore', 'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
// Retain newlines as <br> tags when displaying the marking guide.
$description = nl2br($description);
$descriptionmarkers = nl2br($descriptionmarkers);
}
if (isset($criterion['error_description'])) {
$descriptionclass .= ' error';
}
$title = $shortname;
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL ||
$mode == gradingform_guide_controller::DISPLAY_PREVIEW) {
$title .= html_writer::tag('label', get_string('descriptionstudents', 'gradingform_guide'),
array('for'=>'{NAME}[criteria][{CRITERION-id}][description]'));
$title .= $description;
$title .= html_writer::tag('label', get_string('descriptionmarkers', 'gradingform_guide'),
array('for'=>'{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
$title .= $descriptionmarkers;
$title .= html_writer::tag('label', get_string('maxscore', 'gradingform_guide'),
array('for'=>'{NAME}[criteria][{CRITERION-id}][maxscore]'));
$title .= $maxscore;
} else if ($mode == gradingform_guide_controller::DISPLAY_PREVIEW_GRADED ||
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
$title .= $description;
if (!empty($options['showmarkspercriterionstudents'])) {
$title .= html_writer::label(get_string('maxscore', 'gradingform_guide'), null);
$title .= $maxscore;
}
} else {
$title .= $description . $descriptionmarkers;
}
// Title cell params.
$titletdparams = array(
'class' => $descriptionclass,
'id' => '{NAME}-criteria-{CRITERION-id}-shortname-cell'
);
if ($mode != gradingform_guide_controller::DISPLAY_EDIT_FULL &&
$mode != gradingform_guide_controller::DISPLAY_EDIT_FROZEN) {
// Set description's cell as tab-focusable.
$titletdparams['tabindex'] = '0';
}
$criteriontemplate .= html_writer::tag('td', $title, $titletdparams);
$currentremark = '';
$currentscore = '';
if (isset($value['remark'])) {
$currentremark = $value['remark'];
}
if (isset($value['score'])) {
$currentscore = $value['score'];
}
// Element ID of the remark text area.
$remarkid = $elementname . '-criteria-' . $criterion['id'] . '-remark';
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
$scoreclass = '';
if (!empty($validationerrors[$criterion['id']]['score'])) {
$scoreclass = 'error';
$currentscore = $validationerrors[$criterion['id']]['score']; // Show invalid score in form.
}
// Grading remark text area parameters.
$remarkparams = array(
'name' => '{NAME}[criteria][{CRITERION-id}][remark]',
'id' => $remarkid,
'cols' => '65', 'rows' => '5', 'class' => 'markingguideremark form-control',
'aria-labelledby' => '{NAME}-remarklabel{CRITERION-id}'
);
// Grading remark text area.
$input = html_writer::tag('textarea', s($currentremark), $remarkparams);
// Show the frequently-used comments chooser only if there are defined entries.
$commentchooser = '';
if (!empty($comments)) {
// Frequently used comments chooser.
$chooserbuttonid = 'criteria-' . $criterion['id'] . '-commentchooser';
$commentchooserparams = array('id' => $chooserbuttonid, 'class' => 'commentchooser btn btn-secondary');
$commentchooser = html_writer::tag('button', get_string('insertcomment', 'gradingform_guide'),
$commentchooserparams);
// Option items for the frequently used comments chooser dialog.
$commentoptions = array();
foreach ($comments as $id => $comment) {
$commentoption = new stdClass();
$commentoption->id = $id;
$commentoption->description = html_to_text(format_text($comment['description'], $comment['descriptionformat']));
$commentoptions[] = $commentoption;
}
// Include string for JS for the comment chooser title.
$this->page->requires->string_for_js('insertcomment', 'gradingform_guide');
// Include comment_chooser module.
$this->page->requires->js_call_amd('gradingform_guide/comment_chooser', 'initialise',
array($criterion['id'], $chooserbuttonid, $remarkid, $commentoptions));
}
// Hidden marking guide remark label.
$remarklabelparams = array(
'class' => 'hidden',
'id' => '{NAME}-remarklabel{CRITERION-id}'
);
$remarklabeltext = get_string('criterionremark', 'gradingform_guide',
format_text($criterion['shortname'], FORMAT_HTML));
$remarklabel = html_writer::label($remarklabeltext, $remarkid, false, $remarklabelparams);
$criteriontemplate .= html_writer::tag('td', $remarklabel . $input . $commentchooser, array('class' => 'remark'));
// Score input and max score.
$scoreinputparams = array(
'type' => 'text',
'name' => '{NAME}[criteria][{CRITERION-id}][score]',
'class' => $scoreclass . ' form-control',
'id' => '{NAME}-criteria-{CRITERION-id}-score',
'size' => '3',
'value' => $currentscore,
'aria-labelledby' => '{NAME}-score-label'
);
$score = html_writer::empty_tag('input', $scoreinputparams);
$score .= html_writer::div('/' . s($criterion['maxscore']));
$criteriontemplate .= html_writer::tag('td', $score, array('class' => 'score'));
} else if ($mode == gradingform_guide_controller::DISPLAY_EVAL_FROZEN) {
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
} else if ($mode == gradingform_guide_controller::DISPLAY_REVIEW ||
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
// Hidden marking guide remark description.
$remarkdescparams = array(
'id' => '{NAME}-criteria-{CRITERION-id}-remark-desc'
);
$remarkdesctext = get_string('criterionremark', 'gradingform_guide', $criterion['shortname']);
$remarkdesc = html_writer::div($remarkdesctext, 'hidden', $remarkdescparams);
// Remarks cell.
$remarkdiv = html_writer::div(s($currentremark));
$remarkcellparams = array(
'class' => 'remark',
'tabindex' => '0',
'id' => '{NAME}-criteria-{CRITERION-id}-remark',
'aria-describedby' => '{NAME}-criteria-{CRITERION-id}-remark-desc'
);
$criteriontemplate .= html_writer::tag('td', $remarkdesc . $remarkdiv, $remarkcellparams);
// Score cell.
if (!empty($options['showmarkspercriterionstudents'])) {
$scorecellparams = array(
'class' => 'score',
'tabindex' => '0',
'id' => '{NAME}-criteria-{CRITERION-id}-score',
'aria-describedby' => '{NAME}-score-label'
);
$scorediv = html_writer::div(s($currentscore) . ' / ' . s($criterion['maxscore']));
$criteriontemplate .= html_writer::tag('td', $scorediv, $scorecellparams);
}
}
$criteriontemplate .= html_writer::end_tag('tr'); // Criterion.
$criteriontemplate = str_replace('{NAME}', $elementname, $criteriontemplate);
$criteriontemplate = str_replace('{CRITERION-id}', $criterion['id'], $criteriontemplate);
return $criteriontemplate;
}
/**
* This function returns html code for displaying criterion. Depending on $mode it may be the
* code to edit guide, to preview the guide, to evaluate somebody or to review the evaluation.
*
* This function may be called from display_guide() to display the whole guide, or it can be
* called by itself to return a template used by JavaScript to add new empty criteria to the
* guide being designed.
* In this case it will use macros like {NAME}, {LEVELS}, {CRITERION-id}, etc.
*
* When overriding this function it is very important to remember that all elements of html
* form (in edit or evaluate mode) must have the name $elementname.
*
* Also JavaScript relies on the class names of elements and when developer changes them
* script might stop working.
*
* @param int $mode guide display mode, one of gradingform_guide_controller::DISPLAY_* {@link gradingform_guide_controller}
* @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
* @param array $comment
* @return string
*/
public function comment_template($mode, $elementname = '{NAME}', $comment = null) {
if ($comment === null || !is_array($comment) || !array_key_exists('id', $comment)) {
$comment = array('id' => '{COMMENT-id}',
'description' => '{COMMENT-description}',
'sortorder' => '{COMMENT-sortorder}',
'class' => '{COMMENT-class}');
} else {
foreach (array('sortorder', 'description', 'class') as $key) {
// Set missing array elements to empty strings to avoid warnings.
if (!array_key_exists($key, $comment)) {
$criterion[$key] = '';
}
}
}
$commenttemplate = html_writer::start_tag('tr', array('class' => 'criterion'. $comment['class'],
'id' => '{NAME}-comments-{COMMENT-id}'));
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL) {
$commenttemplate .= html_writer::start_tag('td', array('class' => 'controls'));
foreach (array('moveup', 'delete', 'movedown') as $key) {
$value = get_string('comments'.$key, 'gradingform_guide');
$button = html_writer::empty_tag('input', array('type' => 'submit',
'name' => '{NAME}[comments][{COMMENT-id}]['.$key.']', 'id' => '{NAME}-comments-{COMMENT-id}-'.$key,
'value' => $value, 'title' => $value));
$commenttemplate .= html_writer::tag('div', $button, array('class' => $key));
}
$commenttemplate .= html_writer::end_tag('td'); // Controls.
$commenttemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[comments][{COMMENT-id}][sortorder]', 'value' => $comment['sortorder']));
$description = html_writer::tag('textarea', s($comment['description']),
array('name' => '{NAME}[comments][{COMMENT-id}][description]',
'id' => '{NAME}-comments-{COMMENT-id}-description',
'aria-labelledby' => '{NAME}-comment-label', 'cols' => '65', 'rows' => '5'));
$description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
} else {
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FROZEN) {
$commenttemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[comments][{COMMENT-id}][sortorder]', 'value' => $comment['sortorder']));
$commenttemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[comments][{COMMENT-id}][description]', 'value' => $comment['description']));
}
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
$description = html_writer::tag('span', s($comment['description']),
array('name' => '{NAME}[comments][{COMMENT-id}][description]',
'title' => get_string('clicktocopy', 'gradingform_guide'),
'id' => '{NAME}[comments][{COMMENT-id}]', 'class'=>'markingguidecomment'));
} else {
$description = format_text($comment['description'], $comment['descriptionformat']);
}
// Retain newlines as <br> tags when displaying 'frequently used comments'.
$description = nl2br($description);
}
$descriptionclass = 'description';
if (isset($comment['error_description'])) {
$descriptionclass .= ' error';
}
$descriptioncellparams = array(
'class' => $descriptionclass,
'id' => '{NAME}-comments-{COMMENT-id}-description-cell'
);
// Make description cell tab-focusable when in review mode.
if ($mode != gradingform_guide_controller::DISPLAY_EDIT_FULL &&
$mode != gradingform_guide_controller::DISPLAY_EDIT_FROZEN) {
$descriptioncellparams['tabindex'] = '0';
}
$commenttemplate .= html_writer::tag('td', $description, $descriptioncellparams);
$commenttemplate .= html_writer::end_tag('tr'); // Criterion.
$commenttemplate = str_replace('{NAME}', $elementname, $commenttemplate);
$commenttemplate = str_replace('{COMMENT-id}', $comment['id'], $commenttemplate);
return $commenttemplate;
}
/**
* This function returns html code for displaying guide template (content before and after
* criteria list). Depending on $mode it may be the code to edit guide, to preview the guide,
* to evaluate somebody or to review the evaluation.
*
* This function is called from display_guide() to display the whole guide.
*
* When overriding this function it is very important to remember that all elements of html
* form (in edit or evaluate mode) must have the name $elementname.
*
* Also JavaScript relies on the class names of elements and when developer changes them
* script might stop working.
*
* @param int $mode guide display mode, one of gradingform_guide_controller::DISPLAY_* {@link gradingform_guide_controller}
* @param array $options An array of options provided to {@link gradingform_guide_renderer::guide_edit_options()}
* @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
* @param string $criteriastr evaluated templates for this guide's criteria
* @param string $commentstr
* @return string
*/
protected function guide_template($mode, $options, $elementname, $criteriastr, $commentstr) {
$classsuffix = ''; // CSS suffix for class of the main div. Depends on the mode.
switch ($mode) {
case gradingform_guide_controller::DISPLAY_EDIT_FULL:
$classsuffix = ' editor editable';
break;
case gradingform_guide_controller::DISPLAY_EDIT_FROZEN:
$classsuffix = ' editor frozen';
break;
case gradingform_guide_controller::DISPLAY_PREVIEW:
case gradingform_guide_controller::DISPLAY_PREVIEW_GRADED:
$classsuffix = ' editor preview';
break;
case gradingform_guide_controller::DISPLAY_EVAL:
$classsuffix = ' evaluate editable';
break;
case gradingform_guide_controller::DISPLAY_EVAL_FROZEN:
$classsuffix = ' evaluate frozen';
break;
case gradingform_guide_controller::DISPLAY_REVIEW:
$classsuffix = ' review';
break;
case gradingform_guide_controller::DISPLAY_VIEW:
$classsuffix = ' view';
break;
}
$guidetemplate = html_writer::start_tag('div', array('id' => 'guide-{NAME}',
'class' => 'clearfix gradingform_guide'.$classsuffix));
// Hidden guide label.
$guidedescparams = array(
'id' => 'guide-{NAME}-desc',
'aria-hidden' => 'true'
);
$guidetemplate .= html_writer::div(get_string('guide', 'gradingform_guide'), 'hidden', $guidedescparams);
// Hidden criterion name label/description.
$guidetemplate .= html_writer::div(get_string('criterionname', 'gradingform_guide'), 'hidden',
array('id' => '{NAME}-criterion-name-label'));
// Hidden score label/description.
$guidetemplate .= html_writer::div(get_string('score', 'gradingform_guide'), 'hidden', array('id' => '{NAME}-score-label'));
// Criteria table parameters.
$criteriatableparams = array(
'class' => 'criteria',
'id' => '{NAME}-criteria',
'aria-describedby' => 'guide-{NAME}-desc');
$guidetemplate .= html_writer::tag('table', $criteriastr, $criteriatableparams);
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL) {
$value = get_string('addcriterion', 'gradingform_guide');
$input = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[criteria][addcriterion]',
'id' => '{NAME}-criteria-addcriterion', 'value' => $value, 'title' => $value));
$guidetemplate .= html_writer::tag('div', $input, array('class' => 'addcriterion'));
}
if (!empty($commentstr)) {
$guidetemplate .= html_writer::div(get_string('comments', 'gradingform_guide'), 'commentheader',
array('id' => '{NAME}-comments-label'));
$guidetemplate .= html_writer::div(get_string('comment', 'gradingform_guide'), 'hidden',
array('id' => '{NAME}-comment-label', 'aria-hidden' => 'true'));
$commentstableparams = array(
'class' => 'comments',
'id' => '{NAME}-comments',
'aria-describedby' => '{NAME}-comments-label');
$guidetemplate .= html_writer::tag('table', $commentstr, $commentstableparams);
}
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL) {
$value = get_string('addcomment', 'gradingform_guide');
$input = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[comments][addcomment]',
'id' => '{NAME}-comments-addcomment', 'value' => $value, 'title' => $value));
$guidetemplate .= html_writer::tag('div', $input, array('class' => 'addcomment'));
}
$guidetemplate .= $this->guide_edit_options($mode, $options);
$guidetemplate .= html_writer::end_tag('div');
return str_replace('{NAME}', $elementname, $guidetemplate);
}
/**
* Generates html template to view/edit the guide options. Expression {NAME} is used in
* template for the form element name
*
* @param int $mode guide display mode, one of gradingform_guide_controller::DISPLAY_* {@link gradingform_guide_controller}
* @param array $options
* @return string
*/
protected function guide_edit_options($mode, $options) {
if ($mode != gradingform_guide_controller::DISPLAY_EDIT_FULL
&& $mode != gradingform_guide_controller::DISPLAY_EDIT_FROZEN
&& $mode != gradingform_guide_controller::DISPLAY_PREVIEW) {
// Options are displayed only for people who can manage.
return;
}
$html = html_writer::start_tag('div', array('class' => 'options'));
$html .= html_writer::tag('div', get_string('guideoptions', 'gradingform_guide'), array('class' => 'optionsheading'));
$attrs = array('type' => 'hidden', 'name' => '{NAME}[options][optionsset]', 'value' => 1, 'class' => 'form-control');
$html .= html_writer::empty_tag('input', $attrs);
foreach ($options as $option => $value) {
$html .= html_writer::start_tag('div', array('class' => 'option '.$option));
$attrs = array('name' => '{NAME}[options]['.$option.']', 'id' => '{NAME}-options-'.$option);
switch ($option) {
case 'sortlevelsasc':
// Display option as dropdown.
$html .= html_writer::tag('span', get_string($option, 'gradingform_guide'), array('class' => 'label'));
$value = (int)(!!$value); // Make sure $value is either 0 or 1.
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL) {
$selectoptions = array(0 => get_string($option.'0', 'gradingform_guide'),
1 => get_string($option.'1', 'gradingform_guide'));
$valuestr = html_writer::select($selectoptions, $attrs['name'], $value, false, array('id' => $attrs['id']));
$html .= html_writer::tag('span', $valuestr, array('class' => 'value'));
// TODO add here button 'Sort levels'.
} else {
$html .= html_writer::tag('span', get_string($option.$value, 'gradingform_guide'),
array('class' => 'value'));
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FROZEN) {
$html .= html_writer::empty_tag('input', $attrs + array('type' => 'hidden', 'value' => $value));
}
}
break;
default:
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FROZEN && $value) {
$html .= html_writer::empty_tag('input', $attrs + array('type' => 'hidden', 'value' => $value));
}
// Display option as checkbox.
$attrs['type'] = 'checkbox';
$attrs['value'] = 1;
if ($value) {
$attrs['checked'] = 'checked';
}
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FROZEN ||
$mode == gradingform_guide_controller::DISPLAY_PREVIEW) {
$attrs['disabled'] = 'disabled';
unset($attrs['name']);
}
$html .= html_writer::empty_tag('input', $attrs);
$html .= html_writer::tag('label', get_string($option, 'gradingform_guide'), array('for' => $attrs['id']));
break;
}
$html .= html_writer::end_tag('div'); // Option.
}
$html .= html_writer::end_tag('div'); // Options.
return $html;
}
/**
* This function returns html code for displaying guide. Depending on $mode it may be the code
* to edit guide, to preview the guide, to evaluate somebody or to review the evaluation.
*
* It is very unlikely that this function needs to be overriden by theme. It does not produce
* any html code, it just prepares data about guide design and evaluation, adds the CSS
* class to elements and calls the functions level_template, criterion_template and
* guide_template
*
* @param array $criteria data about the guide design
* @param array $comments
* @param array $options
* @param int $mode guide display mode, one of gradingform_guide_controller::DISPLAY_* {@link gradingform_guide_controller}
* @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
* @param array $values evaluation result
* @param array $validationerrors
* @return string
*/
public function display_guide($criteria, $comments, $options, $mode, $elementname = null, $values = null,
$validationerrors = null) {
$criteriastr = '';
$cnt = 0;
foreach ($criteria as $id => $criterion) {
$criterion['class'] = $this->get_css_class_suffix($cnt++, count($criteria) -1);
$criterion['id'] = $id;
if (isset($values['criteria'][$id])) {
$criterionvalue = $values['criteria'][$id];
} else {
$criterionvalue = null;
}
$criteriastr .= $this->criterion_template($mode, $options, $elementname, $criterion, $criterionvalue,
$validationerrors, $comments);
}
$cnt = 0;
$commentstr = '';
// Check if comments should be displayed.
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL ||
$mode == gradingform_guide_controller::DISPLAY_EDIT_FROZEN ||
$mode == gradingform_guide_controller::DISPLAY_PREVIEW ||
$mode == gradingform_guide_controller::DISPLAY_EVAL_FROZEN) {
foreach ($comments as $id => $comment) {
$comment['id'] = $id;
$comment['class'] = $this->get_css_class_suffix($cnt++, count($comments) -1);
$commentstr .= $this->comment_template($mode, $elementname, $comment);
}
}
$output = $this->guide_template($mode, $options, $elementname, $criteriastr, $commentstr);
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
$showdesc = get_user_preferences('gradingform_guide-showmarkerdesc', true);
$showdescstud = get_user_preferences('gradingform_guide-showstudentdesc', true);
$checked1 = array();
$checked2 = array();
$checked_s1 = array();
$checked_s2 = array();
$checked = array('checked' => 'checked');
if ($showdesc) {
$checked1 = $checked;
} else {
$checked2 = $checked;
}
if ($showdescstud) {
$checked_s1 = $checked;
} else {
$checked_s2 = $checked;
}
$radio1 = html_writer::tag('input', get_string('showmarkerdesc', 'gradingform_guide'), array('type' => 'radio',
'name' => 'showmarkerdesc',
'value' => "true")+$checked1);
$radio1 = html_writer::tag('label', $radio1);
$radio2 = html_writer::tag('input', get_string('hidemarkerdesc', 'gradingform_guide'), array('type' => 'radio',
'name' => 'showmarkerdesc',
'value' => "false")+$checked2);
$radio2 = html_writer::tag('label', $radio2);
$output .= html_writer::tag('div', $radio1 . $radio2, array('class' => 'showmarkerdesc'));
$radio1 = html_writer::tag('input', get_string('showstudentdesc', 'gradingform_guide'), array('type' => 'radio',
'name' => 'showstudentdesc',
'value' => "true")+$checked_s1);
$radio1 = html_writer::tag('label', $radio1);
$radio2 = html_writer::tag('input', get_string('hidestudentdesc', 'gradingform_guide'), array('type' => 'radio',
'name' => 'showstudentdesc',
'value' => "false")+$checked_s2);
$radio2 = html_writer::tag('label', $radio2);
$output .= html_writer::tag('div', $radio1 . $radio2, array('class' => 'showstudentdesc'));
}
return $output;
}
/**
* Help function to return CSS class names for element (first/last/even/odd) with leading space
*
* @param int $idx index of this element in the row/column
* @param int $maxidx maximum index of the element in the row/column
* @return string
*/
protected function get_css_class_suffix($idx, $maxidx) {
$class = '';
if ($idx == 0) {
$class .= ' first';
}
if ($idx == $maxidx) {
$class .= ' last';
}
if ($idx % 2) {
$class .= ' odd';
} else {
$class .= ' even';
}
return $class;
}
/**
* Displays for the student the list of instances or default content if no instances found
*
* @param array $instances array of objects of type gradingform_guide_instance
* @param string $defaultcontent default string that would be displayed without advanced grading
* @param bool $cangrade whether current user has capability to grade in this context
* @return string
*/
public function display_instances($instances, $defaultcontent, $cangrade) {
$return = '';
if (count($instances)) {
$return .= html_writer::start_tag('div', array('class' => 'advancedgrade'));
$idx = 0;
foreach ($instances as $instance) {
$return .= $this->display_instance($instance, $idx++, $cangrade);
}
$return .= html_writer::end_tag('div');
}
return $return. $defaultcontent;
}
/**
* Displays one grading instance
*
* @param gradingform_guide_instance $instance
* @param int $idx unique number of instance on page
* @param bool $cangrade whether current user has capability to grade in this context
*/
public function display_instance(gradingform_guide_instance $instance, $idx, $cangrade) {
$criteria = $instance->get_controller()->get_definition()->guide_criteria;
$options = $instance->get_controller()->get_options();
$values = $instance->get_guide_filling();
if ($cangrade) {
$mode = gradingform_guide_controller::DISPLAY_REVIEW;
} else {
$mode = gradingform_guide_controller::DISPLAY_VIEW;
}
$output = $this->box($instance->get_controller()->get_formatted_description(), 'gradingform_guide-description').
$this->display_guide($criteria, array(), $options, $mode, 'guide'.$idx, $values);
return $output;
}
/**
* Displays a confirmation message after a regrade has occured
*
* @param string $elementname
* @param int $changelevel
* @param int $value The regrade option that was used
* @return string
*/
public function display_regrade_confirmation($elementname, $changelevel, $value) {
$html = html_writer::start_tag('div', array('class' => 'gradingform_guide-regrade', 'role' => 'alert'));
if ($changelevel<=2) {
$html .= get_string('regrademessage1', 'gradingform_guide');
$selectoptions = array(
0 => get_string('regradeoption0', 'gradingform_guide'),
1 => get_string('regradeoption1', 'gradingform_guide')
);
$html .= html_writer::select($selectoptions, $elementname.'[regrade]', $value, false);
} else {
$html .= get_string('regrademessage5', 'gradingform_guide');
$html .= html_writer::empty_tag('input', array('name' => $elementname.'[regrade]', 'value' => 1, 'type' => 'hidden'));
}
$html .= html_writer::end_tag('div');
return $html;
}
/**
* Generates and returns HTML code to display information box about how guide score is converted to the grade
*
* @param array $scores
* @return string
*/
public function display_guide_mapping_explained($scores) {
$html = '';
if (!$scores) {
return $html;
}
if (isset($scores['modulegrade']) && $scores['maxscore'] != $scores['modulegrade']) {
$html .= $this->box(html_writer::tag('div', get_string('guidemappingexplained', 'gradingform_guide', (object)$scores))
, 'generalbox gradingform_guide-error');
}
return $html;
}
}
+248
View File
@@ -0,0 +1,248 @@
.gradingform_guide-regrade {
padding: 10px;
background: #fdd;
border: 1px solid #f00;
margin-bottom: 10px;
}
.gradingform_guide-restored {
padding: 10px;
background: #ffd;
border: 1px solid #ff0;
margin-bottom: 10px;
}
.gradingform_guide-error {
color: red;
font-weight: bold;
}
.gradingform_guide_editform .status {
font-weight: normal;
text-transform: uppercase;
font-size: 60%;
padding: 0.25em;
border: 1px solid #eee;
}
.gradingform_guide_editform .status.ready {
background-color: #e7f1c3;
border-color: #aea;
}
.gradingform_guide_editform .status.draft {
background-color: #f3f2aa;
border-color: #ee2;
}
.gradingform_guide.editor .criterion .controls,
.gradingform_guide .criterion .description,
.gradingform_guide .criterion .remark {
vertical-align: top;
}
.gradingform_guide.editor .criterion .controls,
.gradingform_guide.editor .criterion .description,
.gradingform_guide.editor .criterion .remark {
padding: 3px;
}
.gradingform_guide .criteria {
height: 100%;
}
.gradingform_guide .criterion {
border: 1px solid #ddd;
overflow: hidden;
}
.gradingform_guide .criterion.even {
background: #f0f0f0;
}
.gradingform_guide .criterion .description {
width: 100%;
}
.gradingform_guide .criterion .description .criterionmaxscore input {
width: 20px;
}
.gradingform_guide .criterion .description .criterionname {
font-weight: bold;
}
.gradingform_guide .criterion label {
font-weight: bold;
padding-right: 5px;
}
.gradingform_guide .plainvalue.empty {
font-style: italic;
color: #aaa;
}
.gradingform_guide .plainvalue.editname {
font-weight: bold;
}
/* Make invisible the buttons 'Move up' for the first criterion and 'Move down' for
the last, because those buttons will make no change */
.gradingform_guide.editor .criterion.first.last .controls .delete input,
.gradingform_guide.editor .criterion.first .controls .moveup input,
.gradingform_guide.editor .criterion.last .controls .movedown input {
display: none;
}
/* replace buttons with images */
.gradingform_guide.editor .delete input,
.gradingform_guide.editor .moveup input,
.gradingform_guide.editor .movedown input {
text-indent: -1000em;
cursor: pointer;
border: none;
}
.gradingform_guide.editor .criterion .controls .delete input {
width: 20px;
height: 16px;
background: transparent url([[pix:t/delete]]) no-repeat center top;
margin-top: 4px;
}
.gradingform_guide.editor .moveup input {
width: 20px;
height: 15px;
background: transparent url([[pix:t/up]]) no-repeat center top;
margin-top: 4px;
}
.gradingform_guide.editor .movedown input {
width: 20px;
height: 15px;
background: transparent url([[pix:t/down]]) no-repeat center top;
margin-top: 4px;
}
.gradingform_guide.editor .addcriterion input,
.gradingform_guide.editor .addcomment input {
background: transparent url([[pix:t/add]]) no-repeat;
display: block;
color: #555;
font-weight: bold;
text-decoration: none;
}
.gradingform_guide.editor .addcriterion input,
.gradingform_guide.editor .addcomment input {
background-position: left 5px top 8px;
height: 30px;
line-height: 29px;
margin-bottom: 14px;
padding-left: 20px;
padding-right: 10px;
}
.gradingform_guide .options .optionsheading {
font-weight: bold;
font-size: 1.1em;
padding-bottom: 5px;
}
.gradingform_guide .options .option {
padding-bottom: 2px;
}
.gradingform_guide .options .option label {
margin-left: 5px;
}
.gradingform_guide .options .option .value {
margin-left: 5px;
font-weight: bold;
}
.gradingform_guide .criterion .description.error {
background: #fdd;
}
/* special classes for elements created by guideeditor.js */
.gradingform_guide.editor .hiddenelement {
display: none;
}
.gradingform_guide.editor .pseudotablink {
background-color: transparent;
border: 0 solid;
height: 1px;
width: 1px;
color: transparent;
padding: 0;
margin: 0;
position: relative;
float: right;
}
.jsenabled .gradingform_guide .markingguidecomment {
cursor: pointer;
}
.jsenabled .gradingform_guide .markingguidecomment:before {
content: url([[pix:t/add]]);
padding-right: 2px;
}
.gradingform_guide .commentheader {
font-weight: bold;
font-size: 1.1em;
padding-bottom: 5px;
}
.jsenabled .gradingform_guide .criterionnamelabel {
display: none;
}
.jsenabled .gradingform_guide .criterionshortname {
font-weight: bold;
}
.gradingform_guide table {
width: 100%;
}
.gradingform_guide .descriptionreadonly {
vertical-align: top;
}
.gradingform_guide .criteriondescriptionmarkers {
width: 300px;
}
.gradingform_guide .markingguideremark {
margin: 0;
width: 100%;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.gradingform_guide .criteriondescriptionscore {
display: inline;
}
.gradingform_guide .score label {
display: block;
}
.gradingform_guide .score input {
margin: 0;
width: auto;
}
.gradingform_guide_comment_chooser {
max-height: 80vh;
overflow-y: auto;
}
.gradingform_guide-frequent-comments {
position: absolute;
top: 7px;
right: 0;
}
+5
View File
@@ -0,0 +1,5 @@
.gradingform_guide-fac {
position: absolute;
right: -5px;
top: 5px;
}
@@ -0,0 +1,57 @@
{{!
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/>.
}}
{{!
@template gradingform_guide/comment_chooser
Moodle comment chooser template for marking guide.
The purpose of this template is to render a list of frequently used comments that can be used by the comment chooser dialog.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* criterionId The criterion ID this chooser template is being generated for.
* comments Array of id / description pairs.
Example context (json):
{
"criterionId": "1",
"comments": [
{
"id": "1",
"description": "Test comment description 1"
},
{
"id": "2",
"description": "Test comment description 2"
}
]
}
}}
<div class="gradingform_guide_comment_chooser" id="comment_chooser">
<div class="list-group">
{{#comments}}
<button class="list-group-item list-group-item-action" id="comment-option-{{criterionId}}-{{id}}" tabindex="0">
{{description}}
</button>
{{/comments}}
</div>
</div>
@@ -0,0 +1,164 @@
{{!
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/>.
}}
{{!
@template gradingform_guide/grades/grader/gradingpanel
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* instanceid: Instance of the module this grading form belongs too
* criterion: A gradeable item in the Marking Guide
* name: Name of the gradeable item
* id: ID of the gradeable item
* description: Description shown to students for this gradeable item
* descriptionmarkers: Description shown to teachers for this gradeable item
* maxscore: Max allowable assinable points for this item
* score: Current score assigned to the learner for this item
* remark: Text input for the teacher to relay to the student
* hascomments: Flag for frequently used comments
* comments: Array of frequently used comments
* description: Description of a frequently used comment
Example context (json):
{
"instanceid": "42",
"criterion": [
{
"name": "Motivation",
"id": 13,
"description": "Show your motivation to rock climbing",
"descriptionmarkers": "Does the student show interest in climbing?",
"maxscore": 37,
"score": 20,
"remark": "That's great!",
"hascomments": true,
"comments": [
{"description": "Great work!"},
{"description": "You should really try it before jumping to conclusions"}
]
}
]
}
}}
<form id="gradingform_guide-{{uniqid}}">
<input type="hidden" name="instanceid" value="{{instanceid}}">
{{#criterion}}
<div class="mb-3 criterion" data-gradingform-guide-role="criterion">
<div class="d-flex align-items-center">
<h5 class="description font-weight-bold mb-0">{{name}}</h5>
<button
class="btn btn-link px-1"
aria-controls="gradingform_guide-{{uniqid}}-criteria-{{id}}-description"
aria-expanded="false"
data-target="#gradingform_guide-{{uniqid}}-criteria-{{id}}-description"
data-toggle="collapse"
type="button"
>
{{# pix }} info, gradingform_guide {{/ pix }}
<span class="sr-only">{{#str}}informationforcriterion, gradingform_guide, {{name}}{{/str}}</span>
</button>
<button class="criterion-toggle btn btn-icon icon-no-margin text-reset p-0 font-weight-bold mb-0 ml-auto"
type="button"
data-toggle="collapse"
data-target="#criteria-{{id}}"
aria-expanded="true"
aria-controls="criteria-{{id}}">
<span class="collapsed-icon">
{{#pix}} t/collapsed, core {{/pix}}
<span class="sr-only">{{#str}} expandcriterion, core_grades {{/str}}</span>
</span>
<span class="expanded-icon">
{{#pix}} t/expanded, core {{/pix}}
<span class="sr-only">{{#str}} collapsecriterion, core_grades {{/str}}</span>
</span>
</button>
</div>
<div class="collapse" id="gradingform_guide-{{uniqid}}-criteria-{{id}}-description">
<div class="border p-3 mb-3 bg-white rounded">
{{{description}}}
{{#descriptionmarkers}}
<hr>
{{{descriptionmarkers}}}
{{/descriptionmarkers}}
</div>
</div>
<div class="collapse show" id="criteria-{{id}}">
<div class="mb-3">
<label for="gradingform_guide-{{uniqid}}-criteria-{{id}}-score">{{#str}}outof, gradingform_guide, {{maxscore}}{{/str}}</label>
<input class="form-control" type="number" name="advancedgrading[criteria][{{id}}][score]" value="{{score}}"
id="gradingform_guide-{{uniqid}}-criteria-{{id}}-score"
aria-describedby="gradingform_guide-{{uniqid}}-help-{{id}}-score"
min="0" max="{{maxscore}}"
aria-label="{{#str}}scoreforcriterion, gradingform_guide, {{name}}{{/str}}">
<span id="gradingform_guide-{{uniqid}}-help-{{id}}-score" aria-hidden="true" class="sr-only">{{!
}}{{#str}}score_help, gradingform_guide, { "criterion": {{# quote }}{{ name }}{{/ quote }}, "maxscore": {{# quote }}{{ maxscore }}{{/ quote }} }{{/str}}
</span>
</div>
<div class="mb-3 ">
<label for="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark">{{#str}}additionalcomments, gradingform_guide{{/str}}</label>
<div class="input-group mb-3 form-inset form-inset-right">
<textarea class="form-control" type="text" name="advancedgrading[criteria][{{id}}][remark]"
id="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark"
aria-describedby="gradingform_guide-{{uniqid}}-help-{{id}}-remark"
aria-label="{{#str}}additionalcommentsforcriterion, gradingform_guide, {{name}}{{/str}}"
data-gradingform-guide-role="remark"
rows="2"
data-max-rows="5"
data-auto-rows
>{{remark}}</textarea>
{{#hascomments}}
<button
class="btn btn-icon form-inset-item icon-no-margin p-0 mt-1 mr-1 text-reset collapsed"
aria-controls="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark-frequent-comments"
aria-expanded="false"
data-toggle="collapse"
data-target="#gradingform_guide-{{uniqid}}-criteria-{{id}}-remark-frequent-comments"
type="button"
>
{{#pix}}plus, gradingform_guide{{/pix}}
<span class="sr-only">{{#str}}commentpickerforcriterion, gradingform_guide, {{name}}{{/str}}</span>
</button>
{{/hascomments}}
</div>
{{#hascomments}}
<div class="collapse" id="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark-frequent-comments">
<div data-gradingform_guide-frequent-comments="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark">
<h6>{{#str}}comments, gradingform_guide{{/str}}</h6>
<div class="list-group">
{{#comments}}
<button type="button" class="list-group-item list-group-item-action" data-gradingform_guide-role="frequent-comment">{{description}}</button>
{{/comments}}
</div>
</div>
</div>
{{/hascomments}}
<span id="gradingform_guide-{{uniqid}}-help-{{id}}-remark" aria-hidden="true" class="sr-only">{{#str}}remark_help, gradingform_guide{{/str}}</span>
</div>
</div>
</div>
{{/criterion}}
</form>
{{#js}}
require(['gradingform_guide/grades/grader/gradingpanel/comments', 'core/auto_rows'], function(Comments, AutoRows) {
Comments.init('gradingform_guide-{{uniqid}}');
AutoRows.init(document.getElementById('gradingform_guide-{{uniqid}}'));
});
{{/js}}
@@ -0,0 +1,100 @@
@gradingform @gradingform_guide @javascript
Feature: Display marking guide information to students
In order for students to see the marking guide information
As a teacher
I should be able to change display settings for marking guide information
Background:
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | name | advancedgradingmethod_submissions |
| assign | C1 | Assign 1 | guide |
And I am on the "Course 1" course page logged in as teacher1
And I go to "Assign 1" advanced grading definition page
And I set the following fields to these values:
| Name | Assign 1 marking guide |
| Description | Marking guide description |
And I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Grade Criteria 1 | Grade 1 description for students | Grade 1 description for markers | 70 |
| Grade Criteria 2 | Grade 2 description for students | Grade 2 description for markers | 30 |
And I press "Save marking guide and make it ready"
Scenario: Confirm that marking guide information is not displayed after student is graded
# Update the existing marking guide to ensure that marks per criterion is displayed.
Given I click on "Edit the current form definition" "link"
And I set the field "Show marks per criterion to students" to "0"
And I press "Save"
And I am on the "Assign 1" "assign activity" page
And I go to "Student 1" "Assign 1" activity advanced grading page
And I grade by filling the marking guide with:
| Grade Criteria 1 | 50 | Excellent work! |
| Grade Criteria 2 | 20 | Try harder |
And I press "Save changes"
When I am on the "Assign 1" "assign activity" page logged in as student1
# Confirm the marking guide information display after student is graded when marking per criterion display is disabled.
# Confirm that overall grade is displayed.
Then I should see "70.00 / 100.00"
And I should see the marking guide information displayed as:
| criteria | description | remark |
| Grade Criteria 1 | Grade 1 description for students | Excellent work! |
| Grade Criteria 2 | Grade 2 description for students | Try harder |
Scenario: Confirm that marking guide information is displayed after student is graded
Given I am on the "Assign 1" "assign activity" page logged in as student1
And I should see "Grade 1 description for students" in the "Grade Criteria 1" "table_row"
And I should see "Grade 2 description for students" in the "Grade Criteria 2" "table_row"
# No grade to student1 yet.
And I should not see "70.00 / 100.00"
# No need to update marking guide as marking guide definition is already enabled by default
And I am on the "Assign 1" "assign activity" page logged in as teacher1
And I go to "Student 1" "Assign 1" activity advanced grading page
And I grade by filling the marking guide with:
| Grade Criteria 1 | 50 | Excellent work! |
| Grade Criteria 2 | 20 | Try harder |
And I press "Save changes"
When I am on the "Assign 1" "assign activity" page logged in as student1
# Student1 grade is now displayed.
Then I should see "70.00 / 100.00"
And I should see the marking guide information displayed as:
| criteria | description | remark | maxscore | criteriascore |
| Grade Criteria 1 | Grade 1 description for students | Excellent work! | 70 | 50 / 70 |
| Grade Criteria 2 | Grade 2 description for students | Try harder | 30 | 20 / 30 |
Scenario: Confirm that marking guide definition is retained when grading method is changed
Given I am on the "Assign 1" "assign activity" page
And I go to "Student 1" "Assign 1" activity advanced grading page
And I grade by filling the marking guide with:
| Grade Criteria 1 | 70 | Well done! |
| Grade Criteria 2 | 20 | Great work |
And I press "Save changes"
And I am on the "Assign 1" "assign activity editing" page
And I set the following fields to these values:
| Grading method | Simple direct grading |
And I press "Save and return to course"
When I go to "Assign 1" advanced grading page
Then I should not see "Assign 1 marking guide Ready for use"
And I should not see "Grade Critera 1"
And I should not see "Grade Critera 2"
And I am on the "Course 1" "grades > Grader report > View" page
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- |
| Student 1 | student1@example.com | 90 |
And I am on the "Assign 1" "assign activity editing" page
And I set the following fields to these values:
| Grading method | Marking guide |
And I press "Save and return to course"
And I go to "Assign 1" advanced grading page
And I should see "Assign 1 marking guide Ready for use"
And I should see "Grade Criteria 1"
And I should see "Grade Criteria 2"
@@ -0,0 +1,216 @@
<?php
// 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/>.
/**
* Steps definitions for marking guides.
*
* @package gradingform_guide
* @category test
* @copyright 2015 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
Behat\Mink\Exception\ExpectationException as ExpectationException;
/**
* Steps definitions to help with marking guides.
*
* @package gradingform_guide
* @category test
* @copyright 2015 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_gradingform_guide extends behat_base {
/**
* Defines the marking guide with the provided data, following marking guide's definition grid cells.
*
* This method fills the marking guide of the marking guide definition
* form; the provided TableNode should contain one row for
* each criterion and each cell of the row should contain:
* # Criterion name, a.k.a. shortname
* # Description for students
* # Description for markers
* # Max score
*
* Works with both JS and non-JS.
*
* @When /^I define the following marking guide:$/
* @throws ExpectationException
* @param TableNode $guide
*/
public function i_define_the_following_marking_guide(TableNode $guide) {
$steptableinfo = '| Criterion name | Description for students | Description for markers | Maximum score |';
if ($criteria = $guide->getHash()) {
$addcriterionbutton = $this->find_button(get_string('addcriterion', 'gradingform_guide'));
foreach ($criteria as $index => $criterion) {
// Make sure the criterion array has 4 elements.
if (count($criterion) != 4) {
throw new ExpectationException(
'The criterion definition should contain name, description for students and markers, and maximum points. ' .
'Please follow this format: ' . $steptableinfo,
$this->getSession()
);
}
// On load, there's already a criterion template ready.
$shortnamevisible = false;
if ($index > 0) {
// So if the index is greater than 0, we click the Add new criterion button to add a new criterion.
$addcriterionbutton->click();
$shortnamevisible = true;
}
$criterionroot = 'guide[criteria][NEWID' . ($index + 1) . ']';
// Set the field value for the Criterion name.
$this->set_guide_field_value($criterionroot . '[shortname]', $criterion['Criterion name'], $shortnamevisible);
// Set the field value for the Description for students field.
$this->set_guide_field_value($criterionroot . '[description]', $criterion['Description for students']);
// Set the field value for the Description for markers field.
$this->set_guide_field_value($criterionroot . '[descriptionmarkers]', $criterion['Description for markers']);
// Set the field value for the Max score field.
$this->set_guide_field_value($criterionroot . '[maxscore]', $criterion['Maximum score']);
}
}
}
/**
* Defines the marking guide with the provided data, following marking guide's definition grid cells.
*
* This method fills the table of frequently used comments of the marking guide definition form.
* The provided TableNode should contain one row for each frequently used comment.
* Each row contains:
* # Comment
*
* Works with both JS and non-JS.
*
* @When /^I define the following frequently used comments:$/
* @throws ExpectationException
* @param TableNode $commentstable
*/
public function i_define_the_following_frequently_used_comments(TableNode $commentstable) {
$steptableinfo = '| Comment |';
if ($comments = $commentstable->getRows()) {
$addcommentbutton = $this->find_button(get_string('addcomment', 'gradingform_guide'));
foreach ($comments as $index => $comment) {
// Make sure the comment array has only 1 element.
if (count($comment) != 1) {
throw new ExpectationException(
'The comment cannot be empty. Please follow this format: ' . $steptableinfo,
$this->getSession()
);
}
// On load, there's already a comment template ready.
$commentfieldvisible = false;
if ($index > 0) {
// So if the index is greater than 0, we click the Add frequently used comment button to add a new criterion.
$addcommentbutton->click();
$commentfieldvisible = true;
}
$commentroot = 'guide[comments][NEWID' . ($index + 1) . ']';
// Set the field value for the frequently used comment.
$this->set_guide_field_value($commentroot . '[description]', $comment[0], $commentfieldvisible);
}
}
}
/**
* Performs grading of the student by filling out the marking guide.
* Set one line per criterion and for each criterion set "| Criterion name | Points | Remark |".
*
* @When /^I grade by filling the marking guide with:$/
*
* @throws ExpectationException
* @param TableNode $guide
* @return void
*/
public function i_grade_by_filling_the_marking_guide_with(TableNode $guide) {
$criteria = $guide->getRowsHash();
$stepusage = '"I grade by filling the rubric with:" step needs you to provide a table where each row is a criterion' .
' and each criterion has 3 different values: | Criterion name | Number of points | Remark text |';
// First element -> name, second -> points, third -> Remark.
foreach ($criteria as $name => $criterion) {
// We only expect the points and the remark, as the criterion name is $name.
if (count($criterion) !== 2) {
throw new ExpectationException($stepusage, $this->getSession());
}
// Numeric value here.
$points = $criterion[0];
if (!is_numeric($points)) {
throw new ExpectationException($stepusage, $this->getSession());
}
$criterionid = 0;
if ($criterionnamediv = $this->find('xpath', "//div[@class='criterionshortname'][text()='$name']")) {
$criteriondivname = $criterionnamediv->getAttribute('name');
// Criterion's name is of the format "advancedgrading[criteria][ID][shortname]".
// So just explode the string with "][" as delimiter to extract the criterion ID.
if ($nameparts = explode('][', $criteriondivname)) {
$criterionid = $nameparts[1];
}
}
if ($criterionid) {
$criterionroot = 'advancedgrading[criteria]' . '[' . $criterionid . ']';
$this->execute('behat_forms::i_set_the_field_to', array($criterionroot . '[score]', $points));
$this->execute('behat_forms::i_set_the_field_to', array($criterionroot . '[remark]', $criterion[1]));
}
}
}
/**
* Makes a hidden marking guide field visible (if necessary) and sets a value on it.
*
* @param string $name The name of the field
* @param string $value The value to set
* @param bool $visible
* @return void
*/
protected function set_guide_field_value($name, $value, $visible = false) {
// Fields are hidden by default.
if ($this->running_javascript() && $visible === false) {
$xpath = "//*[@name='$name']/following-sibling::*[contains(concat(' ', normalize-space(@class), ' '), ' plainvalue ')]";
$textnode = $this->find('xpath', $xpath);
$textnode->click();
}
// Set the value now.
$field = $this->find_field($name);
$field->setValue($value);
}
}
@@ -0,0 +1,74 @@
@gradingform @gradingform_guide
Feature: Teacher can define a marking guide
As a teacher,
I should be able to define a marking guide
Background:
Given the following "users" exist:
| username | firtname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | course | name | advancedgradingmethod_submissions |
| assign | C1 | Assign 1 | guide |
And I am on the "Course 1" course page logged in as teacher1
And I go to "Assign 1" advanced grading definition page
And I set the following fields to these values:
| Name | Marking guide 1 |
Scenario: No criterion added to marking guide
When I press "Save as draft"
# Confirm that criterion parameters are required
Then I should see "Criterion name can not be empty"
And I should see "Criterion max score can not be empty"
# Confirm that marking guide is not saved due to the missing criterion
And I should not see "Marking guide 1 Draft"
And I should not see "Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status."
@javascript
Scenario: Marking guide criterion is added to marking guide
Given I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Criteria 1 | Criteria 1 description for student | Criteria 1 description for marker | 70 |
| Criteria 2 | Criteria 2 description for student | Criteria 2 description for marker | 30 |
# Move Criteria 1 below Criteria 2
And I click on "Move down" "button" in the "Criteria 1" "table_row"
When I press "Save as draft"
And I go to "Assign 1" advanced grading definition page
# Confirm that the order of criterion shown matches input -- Criteria 2 is listed before Criteria 1
Then "Move down" "button" in the "Criteria 2" "table_row" should be visible
And "Move up" "button" in the "Criteria 2" "table_row" should not be visible
And "Move up" "button" in the "Criteria 1" "table_row" should be visible
And "Move down" "button" in the "Criteria 1" "table_row" should not be visible
# Confirm the other information entered were saved
And I should see "Criteria 2 description for student" in the "Criteria 2" "table_row"
And I should see "Criteria 2 description for marker" in the "Criteria 2" "table_row"
And I should see "30" in the "Criteria 2" "table_row"
And I should see "Criteria 1 description for student" in the "Criteria 1" "table_row"
And I should see "Criteria 1 description for marker" in the "Criteria 1" "table_row"
And I should see "70" in the "Criteria 1" "table_row"
Scenario: Marking guide options and frequently used comment are added to marking guide
Given I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Criteria 1 | Criteria 1 description for student | Criteria 1 description for marker | 50 |
| Criteria 2 | Criteria 2 description for student | Criteria 2 description for marker | 50 |
# Add frequently used comments and other marking guide options
And I define the following frequently used comments:
| Comment 1 |
| Comment 2 |
And I set the following fields to these values:
| Show guide definition to students | 1 |
| Show marks per criterion to students | 0 |
When I press "Save as draft"
And I go to "Assign 1" advanced grading definition page
# Confirm that frequently used comments and marking guide options specified during registration are retained
Then I should see "Comment 1"
And I should see "Comment 2"
And the field "Show guide definition to students" matches value "1"
And the field "Show marks per criterion to students" matches value "0"
@@ -0,0 +1,53 @@
@gradingform @gradingform_guide
Feature: Teacher can delete marking guide
As a teacher,
I should be able to delete a marking guide
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
| student1 | Student | One | student1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | name | advancedgradingmethod_submissions |
| assign | C1 | Assign 1 | guide |
And I am on the "Course 1" course page logged in as teacher1
And I go to "Assign 1" advanced grading definition page
And I set the following fields to these values:
| Name | Marking guide 1 |
And I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Criterion 1 | Criterion 1 description for student | Criterion 1 description for markers | 100 |
And I press "Save marking guide and make it ready"
@javascript
Scenario: Delete a marking guide
Given I am on the "Assign 1" "assign activity" page
And I go to "Student One" "Assign 1" activity advanced grading page
And I grade by filling the marking guide with:
| Criterion 1 | 70 | Well done! |
And I press "Save changes"
And I go to "Assign 1" advanced grading page
When I click on "Delete the currently defined form" "link"
Then I should see "You are going to delete the grading form 'Marking guide 1' and all the associated information from 'Assign 1 (Submissions)'"
And I press "Cancel"
# Confirm that marking guide was not deleted if Cancel is pressed
And I should see "Marking guide 1 Ready for use"
And I should see "Criterion 1"
And I click on "Delete the currently defined form" "link"
And I press "Continue"
# Confirm that marking guide was deleted successfully if Continue is pressed
And I should see "Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status."
And I should not see "Marking guide 1 Ready for use"
And I should not see "Criterion 1"
And I am on the "Course 1" "grades > Grader report > View" page
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- |
| Student One | student1@example.com | 70 |
@@ -0,0 +1,115 @@
@gradingform @gradingform_guide
Feature: Marking guides can be created and edited
In order to use and refine marking guide to grade students
As a teacher
I need to edit previously used marking guides
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exists:
| activity | assign |
| course | C1 |
| idnumber | assign1 |
| name | Test assignment 1 name |
| intro | Test assignment description |
| section | 1 |
| assignsubmission_file_enabled | 1 |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_maxfiles | 1 |
| assignsubmission_file_maxsizebytes | 1000 |
| assignfeedback_comments_enabled | 1 |
| assignfeedback_file_enabled | 1 |
| assignfeedback_comments_commentinline | 1 |
And I am on the "Test assignment 1 name" "assign activity editing" page logged in as teacher1
And I set the following fields to these values:
| Grading method | Marking guide |
And I press "Save and return to course"
# Defining a marking guide
When I go to "Test assignment 1 name" advanced grading definition page
And I change window size to "large"
And I set the following fields to these values:
| Name | Assignment 1 marking guide |
| Description | Marking guide test description |
And I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Guide criterion A | Guide A description for students | Guide A description for markers | 30 |
| Guide criterion B | Guide B description for students | Guide B description for markers | 30 |
| Guide criterion C | Guide C description for students | Guide C description for markers | 40 |
And I define the following frequently used comments:
| Comment 1 |
| Comment 2 |
| Comment 3 |
| Comment "4" |
And I press "Save marking guide and make it ready"
Then I should see "Ready for use"
And I should see "Guide criterion A"
And I should see "Guide criterion B"
And I should see "Guide criterion C"
And I should see "Comment 1"
And I should see "Comment 2"
And I should see "Comment 3"
And I should see "Comment \"4\""
@javascript
Scenario: Deleting criterion and comment
# Deleting criterion
When I am on "Course 1" course homepage
And I go to "Test assignment 1 name" advanced grading definition page
And I click on "Delete criterion" "button" in the "Guide criterion B" "table_row"
And I press "Yes"
And I press "Save"
Then I should see "Guide criterion A"
And I should see "Guide criterion C"
And I should see "WARNING: Your marking guide has a maximum grade of 70 points"
But I should not see "Guide criterion B"
# Deleting a frequently used comment
When I am on "Course 1" course homepage
And I go to "Test assignment 1 name" advanced grading definition page
And I click on "Delete comment" "button" in the "Comment 3" "table_row"
And I press "Yes"
And I press "Save"
Then I should see "Comment 1"
And I should see "Comment 2"
And I should see "Comment \"4\""
But I should not see "Comment 3"
@javascript
Scenario: Grading and viewing graded marking guide
# Grading a student.
When I navigate to "Assignment" in current page administration
And I go to "Student 1" "Test assignment 1 name" activity advanced grading page
And I grade by filling the marking guide with:
| Guide criterion A | 25 | Very good |
| Guide criterion B | 20 | |
| Guide criterion C | 35 | Nice! |
# Inserting frequently used comment.
And I click on "Insert frequently used comment" "button" in the "Guide criterion B" "table_row"
And I wait "1" seconds
And I press "Comment \"4\""
And I wait "1" seconds
Then the field "Guide criterion B criterion remark" matches value "Comment \"4\""
When I press "Save changes"
And I am on the "Test assignment 1 name" "assign activity" page
And I follow "View all submissions"
# Checking that the user grade is correct.
Then I should see "80" in the "Student 1" "table_row"
And I log out
# Viewing it as a student.
And I am on the "Test assignment 1 name" "assign activity" page logged in as student1
And I should see "80" in the ".feedback" "css_element"
And I should see "Marking guide test description" in the ".feedback" "css_element"
And I should see "Very good"
And I should see "Comment \"4\""
And I should see "Nice!"
Scenario: I can use marking guides to grade and edit them later updating students grades with Javascript disabled
@@ -0,0 +1,39 @@
@gradingform @gradingform_guide
Feature: Teacher can edit a marking guide state
In order to change marking guide back to draft
As a teacher
I need to be able to edit the marking guide status
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | course | name | advancedgradingmethod_submissions |
| assign | C1 | Assign 1 | guide |
Scenario: Marking guide state can be changed to draft
Given I am on the "Course 1" course page logged in as teacher1
And I go to "Assign 1" advanced grading definition page
And I set the following fields to these values:
| Name | Assign 1 marking guide |
| Description | Marking guide description |
And I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Grade Criteria 1 | Grade 1 description for students | Grade 1 description for markers | 70 |
| Grade Criteria 2 | Grade 2 description for students | Grade 2 description for markers | 30 |
And I press "Save marking guide and make it ready"
And I should not see "Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status."
And I should not see "Assign 1 marking guide Draft"
And I should see "Assign 1 marking guide Ready for use"
And I click on "Edit the current form definition" "link"
When I press "Save as draft"
Then I should see "Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status."
And I should see "Assign 1 marking guide Draft"
And I should not see "Assign 1 marking guide Ready for use"
@@ -0,0 +1,51 @@
@gradingform @gradingform_guide
Feature: Publish guide as templates
In order to save time to teachers
As a manager
I need to publish guides and make them available to all teachers
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| manager1 | Manager | 1 | manager1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "activities" exist:
| activity | course | idnumber | name | intro | advancedgradingmethod_submissions |
| assign | C1 | A1 | Test assignment 1 name | TA1 | guide |
| assign | C1 | A2 | Test assignment 2 name | TA2 | guide |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "system role assigns" exist:
| user | role | contextlevel | reference |
| manager1 | manager | System | |
And I log in as "manager1"
And I am on "Course 1" course homepage
And I go to "Test assignment 1 name" advanced grading definition page
And I set the following fields to these values:
| Name | Assignment 1 marking guide |
| Description | Marking guide test description |
And I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Guide criterion A | Guide A description for students | Guide A description for markers | 40 |
| Guide criterion B | Guide B description for students | Guide B description for markers | 60 |
And I define the following frequently used comments:
| Comment 1 |
And I press "Save marking guide and make it ready"
And I publish "Test assignment 1 name" grading form definition as a public template
And I log out
Scenario: Pick grading form from public template
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I go to "Test assignment 2 name" advanced grading page
And I set "Test assignment 2 name" activity to use "Assignment 1 marking guide" grading form
Then I should see "Ready for use"
And I should see "Assignment 1 marking guide"
And I should see "Marking guide test description"
And I should see "Guide criterion A"
And I should see "Guide criterion B"
And I should see "Comment 1"
@@ -0,0 +1,114 @@
<?php
// 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/>.
/**
* Generator for the gradingforum_guide plugin.
*
* @package gradingform_guide
* @category test
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tests\gradingform_guide\generator;
/**
* Convenience class to create guide criterion.
*
* @package gradingform_guide
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class criterion {
/** @var string $shortname A shortened name of the criterion. */
private $shortname;
/** @var string $description A description of the criterion. */
private $description;
/** @var string $descriptionmarkers A description of the criterion for markers. */
private $descriptionmarkers;
/** @var float Maximum score */
private $maxscore = 0;
/**
* Constructor for this test_criterion object
*
* @param string $shortname The shortname for the criterion
* @param string $description The description for the criterion
* @param string $descriptionmarkers The description for the marker for this criterion
* @param float $maxscore The maximum score possible for this criterion
*/
public function __construct(string $shortname, string $description, string $descriptionmarkers, float $maxscore) {
$this->shortname = $shortname;
$this->description = $description;
$this->descriptionmarkers = $descriptionmarkers;
$this->maxscore = $maxscore;
}
/**
* Get the description for this criterion.
*
* @return string
*/
public function get_description(): string {
return $this->description;
}
/**
* Get the description for markers of this criterion.
*
* @return string
*/
public function get_descriptionmarkers(): string {
return $this->descriptionmarkers;
}
/**
* Get the shortname for this criterion.
*
* @return string
*/
public function get_shortname(): string {
return $this->shortname;
}
/**
* Get the maxscore for this criterion.
*
* @return float
*/
public function get_maxscore(): float {
return $this->maxscore;
}
/**
* Get all values in an array for use when creating a new guide.
*
* @param int $sortorder
* @return array
*/
public function get_all_values(int $sortorder): array {
return [
'sortorder' => $sortorder,
'shortname' => $this->get_shortname(),
'description' => $this->get_description(),
'descriptionmarkers' => $this->get_descriptionmarkers(),
'maxscore' => $this->get_maxscore(),
];
}
}
@@ -0,0 +1,125 @@
<?php
// 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/>.
/**
* Generator for the gradingforum_guide plugin.
*
* @package gradingform_guide
* @category test
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tests\gradingform_guide\generator;
use gradingform_controller;
use stdClass;
/**
* Test guide.
*
* @package gradingform_guide
* @category test
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class guide {
/** @var array $criteria The criteria for this guide. */
protected $criteria = [];
/** @var string The name of this guide. */
protected $name;
/** @var string A description for this guide. */
protected $description;
/** @var array The guide options. */
protected $options = [];
/**
* Create a new gradingform_guide_generator_criterion.
*
* @param string $name
* @param string $description
*/
public function __construct(string $name, string $description) {
$this->name = $name;
$this->description = $description;
$this->set_option('alwaysshowdefinition', 1);
$this->set_option('showmarkspercriterionstudents', 1);
}
/**
* Creates the guide using the appropriate APIs.
*/
public function get_definition(): stdClass {
return (object) [
'name' => $this->name,
'description_editor' => [
'text' => $this->description,
'format' => FORMAT_HTML,
'itemid' => 1
],
'guide' => [
'criteria' => $this->get_critiera_as_array(),
'options' => $this->options,
'comments' => [],
],
'saveguide' => 'Continue',
'status' => gradingform_controller::DEFINITION_STATUS_READY,
];
}
/**
* Set an option for the rubric.
*
* @param string $key
* @param mixed $value
* @return self
*/
public function set_option(string $key, $value): self {
$this->options[$key] = $value;
return $this;
}
/**
* Adds a criterion to the guide.
*
* @param criterion $criterion The criterion object (class below).
* @return self
*/
public function add_criteria(criterion $criterion): self {
$this->criteria[] = $criterion;
return $this;
}
/**
* Get the criteria as an array for use in creation.
*
* @return array
*/
protected function get_critiera_as_array(): array {
$return = [];
foreach ($this->criteria as $index => $criterion) {
$return["NEWID{$index}"] = $criterion->get_all_values($index + 1);
}
return $return;
}
}
@@ -0,0 +1,239 @@
<?php
// 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/>.
/**
* Generator for the gradingforum_guide plugin.
*
* @package gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/guide.php');
require_once(__DIR__ . '/criterion.php');
use tests\gradingform_guide\generator\guide;
use tests\gradingform_guide\generator\criterion;
/**
* Generator for the gradingforum_guide plugintype.
*
* @package gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradingform_guide_generator extends component_generator_base {
/**
* Create an instance of a marking guide.
*
* @param context $context
* @param string $component
* @param string $area
* @param string $name
* @param string $description
* @param array $criteria The list of criteria to add to the generated guide
* @return gradingform_guide_controller
*/
public function create_instance(
context $context,
string $component,
string $area,
string $name,
string $description,
array $criteria
): gradingform_guide_controller {
global $USER;
if ($USER->id === 0) {
throw new \coding_exception('Creation of a guide must currently be run as a user.');
}
// Fetch the controller for this context/component/area.
$generator = \testing_util::get_data_generator();
$gradinggenerator = $generator->get_plugin_generator('core_grading');
$controller = $gradinggenerator->create_instance($context, $component, $area, 'guide');
// Generate a definition for the supplied guide.
$guide = $this->get_guide($name, $description);
foreach ($criteria as $name => $options) {
$guide->add_criteria($this->get_criterion(
$name,
$options['description'],
$options['descriptionmarkers'],
$options['maxscore']
));
}
// Update the controller wih the guide definition.
$controller->update_definition($guide->get_definition());
return $controller;
}
/**
* Get a new guide for use with the guide controller.
*
* Note: This is just a helper class used to build a new definition. It does not persist the data.
*
* @param string $name
* @param string $description
* @return generator_guide
*/
protected function get_guide(string $name, string $description): guide {
return new \tests\gradingform_guide\generator\guide($name, $description);
}
/**
* Get a new criterion for use with a guide.
*
* Note: This is just a helper class used to build a new definition. It does not persist the data.
*
* @param string $shortname The shortname for the criterion
* @param string $description The description for the criterion
* @param string $descriptionmarkers The description for the marker for this criterion
* @param float $maxscore The maximum score possible for this criterion
* @return criterion
*/
protected function get_criterion(
string $shortname,
string $description,
string $descriptionmarkers,
float $maxscore
): criterion {
return new criterion($shortname, $description, $descriptionmarkers, $maxscore);
}
/**
* Given a controller instance, fetch the level and criterion information for the specified values.
*
* @param gradingform_controller $controller
* @param string $shortname The shortname to match the criterion on
* @return stdClass
*/
public function get_criterion_for_values(gradingform_controller $controller, string $shortname): ?stdClass {
$definition = $controller->get_definition();
$criteria = $definition->guide_criteria;
$criterion = array_reduce($criteria, function($carry, $criterion) use ($shortname) {
if ($criterion['shortname'] === $shortname) {
$carry = (object) $criterion;
}
return $carry;
}, null);
return $criterion;
}
/**
* Get submitted form data
*
* @param gradingform_guide_controller $controller
* @param int $itemid
* @param array $values A set of array values where the array key is the name of the criterion, and the value is an
* array with the desired score, and any remark.
*/
public function get_submitted_form_data(gradingform_guide_controller $controller, int $itemid, array $values): array {
$result = [
'itemid' => $itemid,
'criteria' => [],
];
foreach ($values as $criterionname => ['score' => $score, 'remark' => $remark]) {
$criterion = $this->get_criterion_for_values($controller, $criterionname);
$result['criteria'][$criterion->id] = [
'score' => $score,
'remark' => $remark,
];
}
return $result;
}
/**
* Generate a guide controller with sample data required for testing of this class.
*
* @param context_module $context
* @return gradingform_guide_controller
*/
public function get_test_guide(
context_module $context,
string $component = 'mod_assign',
string $areaname = 'submission'
): gradingform_guide_controller {
$generator = \testing_util::get_data_generator();
$gradinggenerator = $generator->get_plugin_generator('core_grading');
$controller = $gradinggenerator->create_instance($context, $component, $areaname, 'guide');
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
$guide = $guidegenerator->get_guide('testguide', 'Description text');
$guide->add_criteria($guidegenerator->get_criterion(
'Spelling mistakes',
'Full marks will be given for no spelling mistakes.',
'Deduct 5 points per spelling mistake made.',
25
));
$guide->add_criteria($guidegenerator->get_criterion(
'Pictures',
'Full marks will be given for including 3 pictures.',
'Give 5 points for each picture present',
15
));
$controller->update_definition($guide->get_definition());
return $controller;
}
/**
* Fetch a set of sample data.
*
* @param gradingform_guide_controller $controller
* @param int $itemid
* @param float $spellingscore
* @param string $spellingremark
* @param float $picturescore
* @param string $pictureremark
* @return array
*/
public function get_test_form_data(
gradingform_guide_controller $controller,
int $itemid,
float $spellingscore,
string $spellingremark,
float $picturescore,
string $pictureremark
): array {
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
return $guidegenerator->get_submitted_form_data($controller, $itemid, [
'Spelling mistakes' => [
'score' => $spellingscore,
'remark' => $spellingremark,
],
'Pictures' => [
'score' => $picturescore,
'remark' => $pictureremark,
],
]);
}
}
@@ -0,0 +1,282 @@
<?php
// 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/>.
/**
* Generator testcase for the gradingforum_guide generator.
*
* @package gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradingform_guide;
use context_module;
use gradingform_controller;
use gradingform_guide_controller;
/**
* Generator testcase for the gradingforum_guide generator.
*
* @package gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class generator_test extends \advanced_testcase {
/**
* Test guide creation.
*/
public function test_guide_creation(): void {
global $DB;
$this->resetAfterTest(true);
// Fetch generators.
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
// Create items required for testing.
$course = $generator->create_course();
$module = $generator->create_module('assign', ['course' => $course]);
$user = $generator->create_user();
$context = context_module::instance($module->cmid);
// Data for testing.
$name = 'myfirstguide';
$description = 'My first guide';
$criteria = [
'Alphabet' => [
'description' => 'How well you know your alphabet',
'descriptionmarkers' => 'Basic literacy: Alphabet',
'maxscore' => 5,
],
'Times tables' => [
'description' => 'How well you know your times-tables',
'descriptionmarkers' => 'Basic numeracy: Multiplication',
'maxscore' => 10,
],
];
// Unit under test.
$this->setUser($user);
$controller = $guidegenerator->create_instance($context, 'mod_assign', 'submission', $name, $description, $criteria);
$this->assertInstanceOf(gradingform_guide_controller::class, $controller);
$definition = $controller->get_definition();
$this->assertEquals('guide', $definition->method);
$this->assertNotEmpty($definition->id);
$this->assertEquals($name, $definition->name);
$this->assertEquals($description, $definition->description);
$this->assertEquals(gradingform_controller::DEFINITION_STATUS_READY, $definition->status);
$this->assertNotEmpty($definition->timecreated);
$this->assertNotEmpty($definition->timemodified);
$this->assertEquals($user->id, $definition->usercreated);
$this->assertNotEmpty($definition->guide_criteria);
$this->assertCount(2, $definition->guide_criteria);
// Check the alphabet criteria.
$criteriaids = array_keys($definition->guide_criteria);
$alphabet = $definition->guide_criteria[$criteriaids[0]];
$this->assertNotEmpty($alphabet['id']);
$this->assertEquals(1, $alphabet['sortorder']);
$this->assertEquals('How well you know your alphabet', $alphabet['description']);
$this->assertEquals('Basic literacy: Alphabet', $alphabet['descriptionmarkers']);
$this->assertEquals(5, $alphabet['maxscore']);
// Check the times tables criteria.
$tables = $definition->guide_criteria[$criteriaids[1]];
$this->assertNotEmpty($tables['id']);
$this->assertEquals(2, $tables['sortorder']);
$this->assertEquals('How well you know your times-tables', $tables['description']);
$this->assertEquals('Basic numeracy: Multiplication', $tables['descriptionmarkers']);
$this->assertEquals(10, $tables['maxscore']);
}
/**
* Test the get_criterion_for_values function.
* This is used for finding criterion and level information within a guide.
*/
public function test_get_criterion_for_values(): void {
global $DB;
$this->resetAfterTest(true);
// Fetch generators.
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
// Create items required for testing.
$course = $generator->create_course();
$module = $generator->create_module('assign', ['course' => $course]);
$user = $generator->create_user();
$context = context_module::instance($module->cmid);
// Data for testing.
$name = 'myfirstguide';
$description = 'My first guide';
$criteria = [
'Alphabet' => [
'description' => 'How well you know your alphabet',
'descriptionmarkers' => 'Basic literacy: Alphabet',
'maxscore' => 5,
],
'Times tables' => [
'description' => 'How well you know your times-tables',
'descriptionmarkers' => 'Basic numeracy: Multiplication',
'maxscore' => 10,
],
];
$this->setUser($user);
$controller = $guidegenerator->create_instance($context, 'mod_assign', 'submission', $name, $description, $criteria);
// Valid criterion.
$result = $guidegenerator->get_criterion_for_values($controller, 'Alphabet', 2);
$this->assertEquals('Alphabet', $result->shortname);
$this->assertEquals('How well you know your alphabet', $result->description);
$this->assertEquals('Basic literacy: Alphabet', $result->descriptionmarkers);
$this->assertEquals(5, $result->maxscore);
// Invalid criterion.
$result = $guidegenerator->get_criterion_for_values($controller, 'Foo', 0);
$this->assertNull($result);
}
/**
* Tests for the get_test_guide function.
*/
public function test_get_test_guide(): void {
global $DB;
$this->resetAfterTest(true);
// Fetch generators.
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
// Create items required for testing.
$course = $generator->create_course();
$module = $generator->create_module('assign', ['course' => $course]);
$user = $generator->create_user();
$context = context_module::instance($module->cmid);
$this->setUser($user);
$guide = $guidegenerator->get_test_guide($context, 'assign', 'submissions');
$definition = $guide->get_definition();
$this->assertEquals('testguide', $definition->name);
$this->assertEquals('Description text', $definition->description);
$this->assertEquals(gradingform_controller::DEFINITION_STATUS_READY, $definition->status);
// Should create a guide with 2 criterion.
$this->assertCount(2, $definition->guide_criteria);
}
/**
* Test the get_submitted_form_data function.
*/
public function test_get_submitted_form_data(): void {
global $DB;
$this->resetAfterTest(true);
// Fetch generators.
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
// Create items required for testing.
$course = $generator->create_course();
$module = $generator->create_module('assign', ['course' => $course]);
$user = $generator->create_user();
$context = context_module::instance($module->cmid);
$this->setUser($user);
$controller = $guidegenerator->get_test_guide($context, 'assign', 'submissions');
$result = $guidegenerator->get_submitted_form_data($controller, 93, [
'Spelling mistakes' => [
'score' => 10,
'remark' => 'Pretty good but you had a couple of errors',
],
'Pictures' => [
'score' => 15,
'remark' => 'Lots of nice pictures!',
]
]);
$this->assertIsArray($result);
$this->assertEquals(93, $result['itemid']);
$this->assertIsArray($result['criteria']);
$this->assertCount(2, $result['criteria']);
$spelling = $guidegenerator->get_criterion_for_values($controller, 'Spelling mistakes');
$this->assertIsArray($result['criteria'][$spelling->id]);
$this->assertEquals(10, $result['criteria'][$spelling->id]['score']);
$this->assertEquals('Pretty good but you had a couple of errors', $result['criteria'][$spelling->id]['remark']);
$pictures = $guidegenerator->get_criterion_for_values($controller, 'Pictures', 2);
$this->assertIsArray($result['criteria'][$pictures->id]);
$this->assertEquals(15, $result['criteria'][$pictures->id]['score']);
$this->assertEquals('Lots of nice pictures!', $result['criteria'][$pictures->id]['remark']);
}
/**
* Test the get_test_form_data function.
*/
public function test_get_test_form_data(): void {
global $DB;
$this->resetAfterTest(true);
// Fetch generators.
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
// Create items required for testing.
$course = $generator->create_course();
$module = $generator->create_module('assign', ['course' => $course]);
$user = $generator->create_user();
$context = context_module::instance($module->cmid);
$this->setUser($user);
$controller = $guidegenerator->get_test_guide($context, 'assign', 'submissions');
// Unit under test.
$result = $guidegenerator->get_test_form_data(
$controller,
1839,
10, 'Propper good speling',
0, 'ASCII art is not a picture'
);
$this->assertIsArray($result);
$this->assertEquals(1839, $result['itemid']);
$this->assertIsArray($result['criteria']);
$this->assertCount(2, $result['criteria']);
$spelling = $guidegenerator->get_criterion_for_values($controller, 'Spelling mistakes');
$this->assertIsArray($result['criteria'][$spelling->id]);
$this->assertEquals(10, $result['criteria'][$spelling->id]['score']);
$this->assertEquals('Propper good speling', $result['criteria'][$spelling->id]['remark']);
$pictures = $guidegenerator->get_criterion_for_values($controller, 'Pictures');
$this->assertIsArray($result['criteria'][$pictures->id]);
$this->assertEquals(0, $result['criteria'][$pictures->id]['score']);
$this->assertEquals('ASCII art is not a picture', $result['criteria'][$pictures->id]['remark']);
}
}
@@ -0,0 +1,398 @@
<?php
// 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/>.
declare(strict_types = 1);
namespace gradingform_guide\grades\grader\gradingpanel\external;
use advanced_testcase;
use coding_exception;
use core_grades\component_gradeitem;
use core_external\external_api;
use mod_forum\local\entities\forum as forum_entity;
use moodle_exception;
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class fetch_test extends advanced_testcase {
/**
* Ensure that an execute with an invalid component is rejected.
*/
public function test_execute_invalid_component(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(coding_exception::class);
$this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component");
fetch::execute('mod_invalid', 1, 'foo', 2);
}
/**
* Ensure that an execute with an invalid itemname on a valid component is rejected.
*/
public function test_execute_invalid_itemname(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(coding_exception::class);
$this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component");
fetch::execute('mod_forum', 1, 'foo', 2);
}
/**
* Ensure that an execute against a different grading method is rejected.
*/
public function test_execute_incorrect_type(): void {
$this->resetAfterTest();
$forum = $this->get_forum_instance([
// Negative numbers mean a scale.
'grade_forum' => 5,
]);
$course = $forum->get_course_record();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->setUser($teacher);
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$this->expectException(moodle_exception::class);
$this->expectExceptionMessage("not configured for advanced grading with a marking guide");
fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
}
/**
* Ensure that an execute against the correct grading method returns the current state of the user.
*/
public function test_execute_fetch_empty(): void {
$this->resetAfterTest();
[
'forum' => $forum,
'controller' => $controller,
'definition' => $definition,
'student' => $student,
'teacher' => $teacher,
] = $this->get_test_data();
$this->setUser($teacher);
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
$result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
$this->assertIsArray($result);
$this->assertArrayHasKey('templatename', $result);
$this->assertEquals('gradingform_guide/grades/grader/gradingpanel', $result['templatename']);
$this->assertArrayHasKey('warnings', $result);
$this->assertIsArray($result['warnings']);
$this->assertEmpty($result['warnings']);
// Test the grade array items.
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertIsInt($result['grade']['timecreated']);
$this->assertArrayHasKey('timemodified', $result['grade']);
$this->assertIsInt($result['grade']['timemodified']);
$this->assertArrayHasKey('usergrade', $result['grade']);
$this->assertEquals('- / 100.00', $result['grade']['usergrade']);
$this->assertArrayHasKey('maxgrade', $result['grade']);
$this->assertIsInt($result['grade']['maxgrade']);
$this->assertEquals(100, $result['grade']['maxgrade']);
$this->assertArrayHasKey('gradedby', $result['grade']);
$this->assertEquals(null, $result['grade']['gradedby']);
$this->assertArrayHasKey('criterion', $result['grade']);
$criteria = $result['grade']['criterion'];
$this->assertCount(count($definition->guide_criteria), $criteria);
foreach ($criteria as $criterion) {
$this->assertArrayHasKey('id', $criterion);
$criterionid = $criterion['id'];
$sourcecriterion = $definition->guide_criteria[$criterionid];
$this->assertArrayHasKey('name', $criterion);
$this->assertEquals($sourcecriterion['shortname'], $criterion['name']);
$this->assertArrayHasKey('maxscore', $criterion);
$this->assertEquals($sourcecriterion['maxscore'], $criterion['maxscore']);
$this->assertArrayHasKey('description', $criterion);
$this->assertEquals($sourcecriterion['description'], $criterion['description']);
$this->assertArrayHasKey('descriptionmarkers', $criterion);
$this->assertEquals($sourcecriterion['descriptionmarkers'], $criterion['descriptionmarkers']);
$this->assertArrayHasKey('score', $criterion);
$this->assertEmpty($criterion['score']);
$this->assertArrayHasKey('remark', $criterion);
$this->assertEmpty($criterion['remark']);
}
}
/**
* Ensure that an execute against the correct grading method returns the current state of the user.
*/
public function test_execute_fetch_graded(): void {
$this->resetAfterTest();
[
'forum' => $forum,
'controller' => $controller,
'definition' => $definition,
'student' => $student,
'teacher' => $teacher,
] = $this->get_test_data();
$this->execute_and_assert_fetch($forum, $controller, $definition, $teacher, $teacher, $student);
}
/**
* Class mates should not get other's grades.
*/
public function test_execute_fetch_does_not_return_data_to_other_students(): void {
$this->resetAfterTest();
[
'forum' => $forum,
'controller' => $controller,
'definition' => $definition,
'student' => $student,
'teacher' => $teacher,
'course' => $course,
] = $this->get_test_data();
$evilstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->expectException(\required_capability_exception::class);
$this->execute_and_assert_fetch($forum, $controller, $definition, $evilstudent, $teacher, $student);
}
/**
* Grades can be returned to graded user.
*/
public function test_execute_fetch_return_data_to_graded_user(): void {
$this->resetAfterTest();
[
'forum' => $forum,
'controller' => $controller,
'definition' => $definition,
'student' => $student,
'teacher' => $teacher,
] = $this->get_test_data();
$this->execute_and_assert_fetch($forum, $controller, $definition, $student, $teacher, $student);
}
/**
* Executes and performs all the assertions of the fetch method with the given parameters.
*/
private function execute_and_assert_fetch($forum, $controller, $definition, $fetcheruser, $grader, $gradeduser) {
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
$this->setUser($grader);
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$grade = $gradeitem->get_grade_for_user($gradeduser, $grader);
$instance = $gradeitem->get_advanced_grading_instance($grader, $grade);
$submissiondata = $guidegenerator->get_test_form_data($controller, (int) $gradeduser->id,
10, 'Propper good speling',
0, 'ASCII art is not a picture'
);
$gradeitem->store_grade_from_formdata($gradeduser, $grader, (object) [
'instanceid' => $instance->get_id(),
'advancedgrading' => $submissiondata,
]);
$this->setUser($fetcheruser);
// Set up some items we need to return on other interfaces.
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id);
$result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
$this->assertIsArray($result);
$this->assertArrayHasKey('templatename', $result);
$this->assertEquals('gradingform_guide/grades/grader/gradingpanel', $result['templatename']);
$this->assertArrayHasKey('warnings', $result);
$this->assertIsArray($result['warnings']);
$this->assertEmpty($result['warnings']);
// Test the grade array items.
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertIsInt($result['grade']['timecreated']);
$this->assertArrayHasKey('timemodified', $result['grade']);
$this->assertIsInt($result['grade']['timemodified']);
$this->assertArrayHasKey('usergrade', $result['grade']);
$this->assertEquals('25.00 / 100.00', $result['grade']['usergrade']);
$this->assertArrayHasKey('maxgrade', $result['grade']);
$this->assertIsInt($result['grade']['maxgrade']);
$this->assertEquals(100, $result['grade']['maxgrade']);
$this->assertArrayHasKey('gradedby', $result['grade']);
$this->assertEquals(fullname($grader), $result['grade']['gradedby']);
$this->assertArrayHasKey('criterion', $result['grade']);
$criteria = $result['grade']['criterion'];
$this->assertCount(count($definition->guide_criteria), $criteria);
foreach ($criteria as $criterion) {
$this->assertArrayHasKey('id', $criterion);
$criterionid = $criterion['id'];
$sourcecriterion = $definition->guide_criteria[$criterionid];
$this->assertArrayHasKey('name', $criterion);
$this->assertEquals($sourcecriterion['shortname'], $criterion['name']);
$this->assertArrayHasKey('maxscore', $criterion);
$this->assertEquals($sourcecriterion['maxscore'], $criterion['maxscore']);
$this->assertArrayHasKey('description', $criterion);
$this->assertEquals($sourcecriterion['description'], $criterion['description']);
$this->assertArrayHasKey('descriptionmarkers', $criterion);
$this->assertEquals($sourcecriterion['descriptionmarkers'], $criterion['descriptionmarkers']);
$this->assertArrayHasKey('score', $criterion);
$this->assertArrayHasKey('remark', $criterion);
}
$this->assertEquals(10, $criteria[0]['score']);
$this->assertEquals('Propper good speling', $criteria[0]['remark']);
$this->assertEquals(0, $criteria[1]['score']);
$this->assertEquals('ASCII art is not a picture', $criteria[1]['remark']);
}
/**
* Get a forum instance.
*
* @param array $config
* @return forum_entity
*/
protected function get_forum_instance(array $config = []): forum_entity {
$this->resetAfterTest();
$datagenerator = $this->getDataGenerator();
$course = $datagenerator->create_course();
$forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id, 'grade_forum' => 100]));
$vaultfactory = \mod_forum\local\container::get_vault_factory();
$vault = $vaultfactory->get_forum_vault();
return $vault->get_from_id((int) $forum->id);
}
/**
* Get test data for forums graded using a marking guide.
*
* @return array
*/
protected function get_test_data(): array {
global $DB;
$this->resetAfterTest();
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
$forum = $this->get_forum_instance();
$course = $forum->get_course_record();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->setUser($teacher);
$controller = $guidegenerator->get_test_guide($forum->get_context(), 'forum', 'forum');
$definition = $controller->get_definition();
// In the situation of mod_forum this would be the id from forum_grades.
$itemid = 1;
$instance = $controller->create_instance($student->id, $itemid);
$data = $this->get_test_form_data(
$controller,
$itemid,
5, 'This user made several mistakes.',
10, 'This user has two pictures.'
);
// Update this instance with data.
$instance->update($data);
return [
'forum' => $forum,
'controller' => $controller,
'definition' => $definition,
'student' => $student,
'teacher' => $teacher,
'course' => $course,
];
}
/**
* Fetch a set of sample data.
*
* @param \gradingform_guide_controller $controller
* @param int $itemid
* @param float $spellingscore
* @param string $spellingremark
* @param float $picturescore
* @param string $pictureremark
* @return array
*/
protected function get_test_form_data(
\gradingform_guide_controller $controller,
int $itemid,
float $spellingscore,
string $spellingremark,
float $picturescore,
string $pictureremark
): array {
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
return $guidegenerator->get_test_form_data(
$controller,
$itemid,
$spellingscore,
$spellingremark,
$picturescore,
$pictureremark
);
}
}
@@ -0,0 +1,245 @@
<?php
// 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/>.
declare(strict_types = 1);
namespace gradingform_guide\grades\grader\gradingpanel\external;
use advanced_testcase;
use coding_exception;
use core_grades\component_gradeitem;
use core_external\external_api;
use mod_forum\local\entities\forum as forum_entity;
use moodle_exception;
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class store_test extends advanced_testcase {
/**
* Ensure that an execute with an invalid component is rejected.
*/
public function test_execute_invalid_component(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(coding_exception::class);
$this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component");
store::execute('mod_invalid', 1, 'foo', 2, false, 'formdata');
}
/**
* Ensure that an execute with an invalid itemname on a valid component is rejected.
*/
public function test_execute_invalid_itemname(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException(coding_exception::class);
$this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component");
store::execute('mod_forum', 1, 'foo', 2, false, 'formdata');
}
/**
* Ensure that an execute against a different grading method is rejected.
*/
public function test_execute_incorrect_type(): void {
$this->resetAfterTest();
$forum = $this->get_forum_instance([
'grade_forum' => 5,
]);
$course = $forum->get_course_record();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->setUser($teacher);
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$this->expectException(moodle_exception::class);
$this->expectExceptionMessage("not configured for advanced grading with a marking guide");
store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata');
}
/**
* Ensure that an execute against a different grading method is rejected.
*/
public function test_execute_disabled(): void {
$this->resetAfterTest();
$forum = $this->get_forum_instance();
$course = $forum->get_course_record();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->setUser($teacher);
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$this->expectException(moodle_exception::class);
$this->expectExceptionMessage("Grading is not enabled");
store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata');
}
/**
* Ensure that an execute against the correct grading method returns the current state of the user.
*/
public function test_execute_store_graded(): void {
$this->resetAfterTest();
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
[
'forum' => $forum,
'controller' => $controller,
'definition' => $definition,
'student' => $student,
'teacher' => $teacher,
] = $this->get_test_data();
$this->setUser($teacher);
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$grade = $gradeitem->get_grade_for_user($student, $teacher);
$instance = $gradeitem->get_advanced_grading_instance($teacher, $grade);
$submissiondata = $guidegenerator->get_test_form_data($controller, (int) $student->id,
10, 'Propper good speling',
0, 'ASCII art is not a picture'
);
$formdata = http_build_query((object) [
'instanceid' => $instance->get_id(),
'advancedgrading' => $submissiondata,
], '', '&');
$result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, $formdata);
$result = external_api::clean_returnvalue(store::execute_returns(), $result);
$this->assertIsArray($result);
$this->assertArrayHasKey('templatename', $result);
$this->assertEquals('gradingform_guide/grades/grader/gradingpanel', $result['templatename']);
$this->assertArrayHasKey('warnings', $result);
$this->assertIsArray($result['warnings']);
$this->assertEmpty($result['warnings']);
// Test the grade array items.
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertIsInt($result['grade']['timecreated']);
$this->assertArrayHasKey('timemodified', $result['grade']);
$this->assertIsInt($result['grade']['timemodified']);
$this->assertArrayHasKey('usergrade', $result['grade']);
$this->assertEquals('0.50 / 2.00', $result['grade']['usergrade']);
$this->assertArrayHasKey('maxgrade', $result['grade']);
$this->assertIsInt($result['grade']['maxgrade']);
$this->assertEquals(2, $result['grade']['maxgrade']);
$this->assertArrayHasKey('gradedby', $result['grade']);
$this->assertEquals(fullname($teacher), $result['grade']['gradedby']);
$this->assertArrayHasKey('criterion', $result['grade']);
$criteria = $result['grade']['criterion'];
$this->assertCount(count($definition->guide_criteria), $criteria);
foreach ($criteria as $criterion) {
$this->assertArrayHasKey('id', $criterion);
$criterionid = $criterion['id'];
$sourcecriterion = $definition->guide_criteria[$criterionid];
$this->assertArrayHasKey('name', $criterion);
$this->assertEquals($sourcecriterion['shortname'], $criterion['name']);
$this->assertArrayHasKey('maxscore', $criterion);
$this->assertEquals($sourcecriterion['maxscore'], $criterion['maxscore']);
$this->assertArrayHasKey('description', $criterion);
$this->assertEquals($sourcecriterion['description'], $criterion['description']);
$this->assertArrayHasKey('descriptionmarkers', $criterion);
$this->assertEquals($sourcecriterion['descriptionmarkers'], $criterion['descriptionmarkers']);
$this->assertArrayHasKey('score', $criterion);
$this->assertArrayHasKey('remark', $criterion);
}
$this->assertEquals(10, $criteria[0]['score']);
$this->assertEquals('Propper good speling', $criteria[0]['remark']);
$this->assertEquals(0, $criteria[1]['score']);
$this->assertEquals('ASCII art is not a picture', $criteria[1]['remark']);
}
/**
* Get a forum instance.
*
* @param array $config
* @return forum_entity
*/
protected function get_forum_instance(array $config = []): forum_entity {
$this->resetAfterTest();
$datagenerator = $this->getDataGenerator();
$course = $datagenerator->create_course();
$forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id]));
$vaultfactory = \mod_forum\local\container::get_vault_factory();
$vault = $vaultfactory->get_forum_vault();
return $vault->get_from_id((int) $forum->id);
}
/**
* Get test data for forums graded using a marking guide.
*
* @return array
*/
protected function get_test_data(): array {
global $DB;
$this->resetAfterTest();
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
$forum = $this->get_forum_instance();
$course = $forum->get_course_record();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->setUser($teacher);
$controller = $guidegenerator->get_test_guide($forum->get_context(), 'forum', 'forum');
$definition = $controller->get_definition();
$DB->set_field('forum', 'grade_forum', count($definition->guide_criteria), ['id' => $forum->get_id()]);
return [
'forum' => $forum,
'controller' => $controller,
'definition' => $definition,
'student' => $student,
'teacher' => $teacher,
];
}
}
@@ -0,0 +1,94 @@
<?php
// 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/>.
namespace gradingform_guide;
use gradingform_controller;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/grade/grading/lib.php');
require_once($CFG->dirroot . '/grade/grading/form/guide/lib.php');
/**
* Test cases for the Marking Guide.
*
* @package gradingform_guide
* @category test
* @copyright 2015 Nikita Kalinin <nixorv@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class guide_test extends \advanced_testcase {
/**
* Unit test to get draft instance and create new instance.
*/
public function test_get_or_create_instance(): void {
global $DB;
$this->resetAfterTest(true);
// Create fake areas.
$fakearea = (object)array(
'contextid' => 1,
'component' => 'mod_assign',
'areaname' => 'submissions',
'activemethod' => 'guide'
);
$fakearea1id = $DB->insert_record('grading_areas', $fakearea);
$fakearea->contextid = 2;
$fakearea2id = $DB->insert_record('grading_areas', $fakearea);
// Create fake definitions.
$fakedefinition = (object)array(
'areaid' => $fakearea1id,
'method' => 'guide',
'name' => 'fakedef',
'status' => gradingform_controller::DEFINITION_STATUS_READY,
'timecreated' => 0,
'usercreated' => 1,
'timemodified' => 0,
'usermodified' => 1,
);
$fakedef1id = $DB->insert_record('grading_definitions', $fakedefinition);
$fakedefinition->areaid = $fakearea2id;
$fakedef2id = $DB->insert_record('grading_definitions', $fakedefinition);
// Create fake guide instance in first area.
$fakeinstance = (object)array(
'definitionid' => $fakedef1id,
'raterid' => 1,
'itemid' => 1,
'rawgrade' => null,
'status' => 0,
'feedback' => null,
'feedbackformat' => 0,
'timemodified' => 0
);
$fakeinstanceid = $DB->insert_record('grading_instances', $fakeinstance);
$manager1 = get_grading_manager($fakearea1id);
$manager2 = get_grading_manager($fakearea2id);
$controller1 = $manager1->get_controller('guide');
$controller2 = $manager2->get_controller('guide');
$instance1 = $controller1->get_or_create_instance(0, 1, 1);
$instance2 = $controller2->get_or_create_instance(0, 1, 1);
// Definitions should not be the same.
$this->assertEquals(false, $instance1->get_data('definitionid') == $instance2->get_data('definitionid'));
}
}
@@ -0,0 +1,217 @@
<?php
// 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/>.
/**
* Privacy tests for gradingform_guide.
*
* @package gradingform_guide
* @category test
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradingform_guide\privacy;
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_privacy\tests\provider_testcase;
use core_privacy\local\request\writer;
use gradingform_guide\privacy\provider;
/**
* Privacy tests for gradingform_guide.
*
* @package gradingform_guide
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends provider_testcase {
/**
* Ensure that export_user_preferences returns no data if the user has no data.
*/
public function test_export_user_preferences_not_defined(): void {
$user = \core_user::get_user_by_username('admin');
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$this->assertFalse($writer->has_any_data());
}
/**
* Ensure that export_user_preferences returns single preferences.
*/
public function test_export_user_preferences(): void {
$this->resetAfterTest();
// Define a user preference.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
set_user_preference('gradingform_guide-showmarkerdesc', 0, $user);
set_user_preference('gradingform_guide-showstudentdesc', 1, $user);
// Validate exported data.
provider::export_user_preferences($user->id);
$context = \context_user::instance($user->id);
/** @var \core_privacy\tests\request\content_writer $writer */
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$prefs = $writer->get_user_preferences('gradingform_guide');
$this->assertCount(2, (array) $prefs);
$this->assertEquals(
get_string('privacy:metadata:preference:showstudentdesc', 'gradingform_guide'),
$prefs->{'gradingform_guide-showstudentdesc'}->description
);
$this->assertEquals(get_string('no'), $prefs->{'gradingform_guide-showmarkerdesc'}->value);
$this->assertEquals(get_string('yes'), $prefs->{'gradingform_guide-showstudentdesc'}->value);
}
/**
* Test the export of guide data.
*/
public function test_get_gradingform_export_data(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$modulecontext = \context_module::instance($module->cmid);
$controller = $this->get_test_guide($modulecontext);
// In the situation of mod_assign this would be the id from assign_grades.
$itemid = 1;
$instance = $controller->create_instance($user->id, $itemid);
$data = $this->get_test_form_data(
$controller,
$itemid,
5, 'This user made several mistakes.',
10, 'This user has two pictures.'
);
$instance->update($data);
$instanceid = $instance->get_data('id');
// Let's try the method we are testing.
provider::export_gradingform_instance_data($modulecontext, $instance->get_id(), ['Test']);
$data = (array) writer::with_context($modulecontext)->get_data(['Test', 'Marking guide', $instanceid]);
$this->assertCount(2, $data);
$this->assertEquals('Spelling mistakes', $data['Spelling mistakes']->shortname);
$this->assertEquals('This user made several mistakes.', $data['Spelling mistakes']->remark);
$this->assertEquals('Pictures', $data['Pictures']->shortname);
$this->assertEquals('This user has two pictures.', $data['Pictures']->remark);
}
/**
* Test the deletion of guide user information via the instance ID.
*/
public function test_delete_gradingform_for_instances(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$modulecontext = \context_module::instance($module->cmid);
$controller = $this->get_test_guide($modulecontext);
// In the situation of mod_assign this would be the id from assign_grades.
$itemid = 1;
$instance = $controller->create_instance($user->id, $itemid);
$data = $this->get_test_form_data(
$controller,
$itemid,
5, 'This user made several mistakes.',
10, 'This user has two pictures.'
);
$instance->update($data);
$instanceid = $instance->get_data('id');
$itemid = 2;
$instance = $controller->create_instance($user->id, $itemid);
$data = $this->get_test_form_data(
$controller,
$itemid,
25, 'This user made no mistakes.',
5, 'This user has one pictures.'
);
$instance->update($data);
$instanceid = $instance->get_data('id');
// Check how many records we have in the fillings table.
$records = $DB->get_records('gradingform_guide_fillings');
$this->assertCount(4, $records);
// Let's delete one of the instances (the last one would be the easiest).
provider::delete_gradingform_for_instances([$instance->get_id()]);
$records = $DB->get_records('gradingform_guide_fillings');
$this->assertCount(2, $records);
foreach ($records as $record) {
$this->assertNotEquals($instance->get_id(), $record->instanceid);
}
}
/**
* Generate a guide controller with sample data required for testing of this class.
*
* @param \context_module $context
* @return \gradingform_guide_controller
*/
protected function get_test_guide(\context_module $context): \gradingform_guide_controller {
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
return $guidegenerator->get_test_guide($context);
}
/**
* Fetch a set of sample data.
*
* @param \gradingform_guide_controller $controller
* @param int $itemid
* @param float $spellingscore
* @param string $spellingremark
* @param float $picturescore
* @param string $pictureremark
* @return array
*/
protected function get_test_form_data(
\gradingform_guide_controller $controller,
int $itemid,
float $spellingscore,
string $spellingremark,
float $picturescore,
string $pictureremark
): array {
$generator = \testing_util::get_data_generator();
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
return $guidegenerator->get_test_form_data(
$controller,
$itemid,
$spellingscore,
$spellingremark,
$picturescore,
$pictureremark
);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
// 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/>.
/**
* Marking guide, advanced grade plugin
*
* @package gradingform_guide
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'gradingform_guide';
$plugin->version = 2024042200;
$plugin->requires = 2024041600;
$plugin->maturity = MATURITY_STABLE;