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
+1
View File
@@ -0,0 +1 @@
The default gradebook interface for students, showing just the user's own grades.
+11
View File
@@ -0,0 +1,11 @@
define("gradereport_user/gradecategorytoggle",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
/**
* Javascript module for toggling the visibility of the grade categories in the user report.
*
* @module gradereport_user/gradecategorytoggle
* @copyright 2022 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const SELECTORS_CATEGORY_TOGGLE=".toggle-category",SELECTORS_USER_REPORT_TABLE=".user-grade",toggleCategory=toggleElement=>{const target=toggleElement.dataset.target,categoryId=toggleElement.dataset.categoryid,isCollapsing="true"===toggleElement.getAttribute("aria-expanded"),targetRows=toggleElement.closest(SELECTORS_USER_REPORT_TABLE).querySelectorAll(target);isCollapsing?(toggleElement.setAttribute("aria-expanded","false"),toggleElement.dataset.target="[data-hidden-by='".concat(categoryId,"']")):(toggleElement.setAttribute("aria-expanded","true"),toggleElement.dataset.target=".cat_".concat(categoryId,"[data-hidden='false']")),targetRows.forEach((row=>{isCollapsing?(row.dataset.hidden="true",row.dataset.hiddenBy=categoryId):(row.dataset.hidden="false",row.dataset.hiddenBy="")})),updateParentCategoryRowspans(toggleElement,targetRows.length)},updateParentCategoryRowspans=(toggleElement,num)=>{const userReport=toggleElement.closest(SELECTORS_USER_REPORT_TABLE);toggleElement.closest("tr").classList.forEach((className=>{const parentCategoryToggleElement=userReport.querySelector('[data-target=".'.concat(className,"[data-hidden='false']\""));if(parentCategoryToggleElement){const categoryRowSpanElement=parentCategoryToggleElement.closest("tr").nextElementSibling.querySelector("[rowspan]");"true"===toggleElement.getAttribute("aria-expanded")?categoryRowSpanElement.rowSpan=categoryRowSpanElement.rowSpan+num:categoryRowSpanElement.rowSpan=categoryRowSpanElement.rowSpan-num}}))};_exports.init=userReportId=>{(userReportId=>{document.querySelector("#"+userReportId).querySelector(SELECTORS_USER_REPORT_TABLE).addEventListener("click",(e=>{const toggle=e.target.closest(SELECTORS_CATEGORY_TOGGLE);toggle&&(e.preventDefault(),toggleCategory(toggle))}))})(userReportId)}}));
//# sourceMappingURL=gradecategorytoggle.min.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("gradereport_user/group",["exports","core_group/comboboxsearch/group","core/url"],(function(_exports,_group,_url){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_group=_interopRequireDefault(_group),_url=_interopRequireDefault(_url);class Group extends _group.default{constructor(){var obj,key,value;super(),value=void 0,(key="courseID")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.selectors={...this.selectors,courseid:'[data-region="courseid"]'};const component=document.querySelector(this.componentSelector());this.courseID=component.querySelector(this.selectors.courseid).dataset.courseid}static init(){return new Group}selectOneLink(groupID){return _url.default.relativeUrl("/grade/report/user/index.php",{id:this.courseID,groupsearchvalue:this.getSearchTerm(),group:groupID},!1)}}return _exports.default=Group,_exports.default}));
//# sourceMappingURL=group.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"group.min.js","sources":["../src/group.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 * Allow the user to search for groups within the user report.\n *\n * @module gradereport_user/group\n * @copyright 2023 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport GroupSearch from 'core_group/comboboxsearch/group';\nimport Url from 'core/url';\n\nexport default class Group extends GroupSearch {\n\n courseID;\n\n constructor() {\n super();\n\n // Define our standard lookups.\n this.selectors = {...this.selectors,\n courseid: '[data-region=\"courseid\"]',\n };\n const component = document.querySelector(this.componentSelector());\n this.courseID = component.querySelector(this.selectors.courseid).dataset.courseid;\n }\n\n static init() {\n return new Group();\n }\n\n /**\n * Build up the link that is dedicated to a particular result.\n *\n * @param {Number} groupID The ID of the group selected.\n * @returns {string|*}\n */\n selectOneLink(groupID) {\n return Url.relativeUrl('/grade/report/user/index.php', {\n id: this.courseID,\n groupsearchvalue: this.getSearchTerm(),\n group: groupID,\n }, false);\n }\n}\n"],"names":["Group","GroupSearch","constructor","selectors","this","courseid","component","document","querySelector","componentSelector","courseID","dataset","selectOneLink","groupID","Url","relativeUrl","id","groupsearchvalue","getSearchTerm","group"],"mappings":"yWAyBqBA,cAAcC,eAI/BC,6LAISC,UAAY,IAAIC,KAAKD,UACtBE,SAAU,kCAERC,UAAYC,SAASC,cAAcJ,KAAKK,0BACzCC,SAAWJ,UAAUE,cAAcJ,KAAKD,UAAUE,UAAUM,QAAQN,8BAIlE,IAAIL,MASfY,cAAcC,gBACHC,aAAIC,YAAY,+BAAgC,CACnDC,GAAIZ,KAAKM,SACTO,iBAAkBb,KAAKc,gBACvBC,MAAON,UACR"}
+10
View File
@@ -0,0 +1,10 @@
define("gradereport_user/user",["exports","core_user/comboboxsearch/user","core/url","core/templates","core_grades/searchwidget/repository"],(function(_exports,_user,_url,_templates,Repository){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Allow the user to search for learners within the user report.
*
* @module gradereport_user/user
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_user=_interopRequireDefault(_user),_url=_interopRequireDefault(_url),Repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Repository);class User extends _user.default{constructor(){super()}static init(){return new User}async renderDropdown(){const{html:html,js:js}=await(0,_templates.renderForPromise)("core_user/comboboxsearch/resultset",{users:this.getMatchedResults().slice(0,5),hasresults:this.getMatchedResults().length>0,instance:this.instance,matches:this.getDatasetSize(),searchterm:this.getSearchTerm(),selectall:this.selectAllResultsLink()});(0,_templates.replaceNodeContents)(this.getHTMLElements().searchDropdown,html,js),this.searchInput.removeAttribute("aria-activedescendant")}selectAllResultsLink(){return _url.default.relativeUrl("/grade/report/user/index.php",{id:this.courseID,userid:0,searchvalue:this.getSearchTerm()},!1)}selectOneLink(userID){return _url.default.relativeUrl("/grade/report/user/index.php",{id:this.courseID,searchvalue:this.getSearchTerm(),userid:userID},!1)}fetchDataset(){const gts="string"==typeof this.groupID&&""===this.groupID?0:this.groupID;return Repository.userFetch(this.courseID,gts).then((r=>r.users))}}return _exports.default=User,_exports.default}));
//# sourceMappingURL=user.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"user.min.js","sources":["../src/user.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 * Allow the user to search for learners within the user report.\n *\n * @module gradereport_user/user\n * @copyright 2023 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport UserSearch from 'core_user/comboboxsearch/user';\nimport Url from 'core/url';\nimport {renderForPromise, replaceNodeContents} from 'core/templates';\nimport * as Repository from 'core_grades/searchwidget/repository';\n\nexport default class User extends UserSearch {\n\n constructor() {\n super();\n }\n\n static init() {\n return new User();\n }\n\n /**\n * Build the content then replace the node.\n */\n async renderDropdown() {\n const {html, js} = await renderForPromise('core_user/comboboxsearch/resultset', {\n users: this.getMatchedResults().slice(0, 5),\n hasresults: this.getMatchedResults().length > 0,\n instance: this.instance,\n matches: this.getDatasetSize(),\n searchterm: this.getSearchTerm(),\n selectall: this.selectAllResultsLink(),\n });\n replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);\n // Remove aria-activedescendant when the available options change.\n this.searchInput.removeAttribute('aria-activedescendant');\n }\n\n /**\n * Build up the view all link.\n *\n * @returns {string|*}\n */\n selectAllResultsLink() {\n return Url.relativeUrl('/grade/report/user/index.php', {\n id: this.courseID,\n userid: 0,\n searchvalue: this.getSearchTerm()\n }, false);\n }\n\n /**\n * Build up the link that is dedicated to a particular result.\n *\n * @param {Number} userID The ID of the user selected.\n * @returns {string|*}\n */\n selectOneLink(userID) {\n return Url.relativeUrl('/grade/report/user/index.php', {\n id: this.courseID,\n searchvalue: this.getSearchTerm(),\n userid: userID,\n }, false);\n }\n\n /**\n * Get the data we will be searching against in this component.\n *\n * @returns {Promise<*>}\n */\n fetchDataset() {\n // Small typing checks as sometimes groups don't exist therefore the element returns a empty string.\n const gts = typeof (this.groupID) === \"string\" && this.groupID === '' ? 0 : this.groupID;\n return Repository.userFetch(this.courseID, gts).then((r) => r.users);\n }\n}\n"],"names":["User","UserSearch","constructor","html","js","users","this","getMatchedResults","slice","hasresults","length","instance","matches","getDatasetSize","searchterm","getSearchTerm","selectall","selectAllResultsLink","getHTMLElements","searchDropdown","searchInput","removeAttribute","Url","relativeUrl","id","courseID","userid","searchvalue","selectOneLink","userID","fetchDataset","gts","groupID","Repository","userFetch","then","r"],"mappings":";;;;;;;q0BA2BqBA,aAAaC,cAE9BC,2CAKW,IAAIF,kCAOLG,KAACA,KAADC,GAAOA,UAAY,+BAAiB,qCAAsC,CAC5EC,MAAOC,KAAKC,oBAAoBC,MAAM,EAAG,GACzCC,WAAYH,KAAKC,oBAAoBG,OAAS,EAC9CC,SAAUL,KAAKK,SACfC,QAASN,KAAKO,iBACdC,WAAYR,KAAKS,gBACjBC,UAAWV,KAAKW,4DAEAX,KAAKY,kBAAkBC,eAAgBhB,KAAMC,SAE5DgB,YAAYC,gBAAgB,yBAQrCJ,8BACWK,aAAIC,YAAY,+BAAgC,CACnDC,GAAIlB,KAAKmB,SACTC,OAAQ,EACRC,YAAarB,KAAKS,kBACnB,GASPa,cAAcC,eACHP,aAAIC,YAAY,+BAAgC,CACnDC,GAAIlB,KAAKmB,SACTE,YAAarB,KAAKS,gBAClBW,OAAQG,SACT,GAQPC,qBAEUC,IAAgC,iBAAlBzB,KAAK0B,SAA0C,KAAjB1B,KAAK0B,QAAiB,EAAI1B,KAAK0B,eAC1EC,WAAWC,UAAU5B,KAAKmB,SAAUM,KAAKI,MAAMC,GAAMA,EAAE/B"}
@@ -0,0 +1,137 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Javascript module for toggling the visibility of the grade categories in the user report.
*
* @module gradereport_user/gradecategorytoggle
* @copyright 2022 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const SELECTORS = {
CATEGORY_TOGGLE: '.toggle-category',
USER_REPORT_TABLE: '.user-grade'
};
/**
* Register related event listeners.
*
* @method registerListenerEvents
* @param {string} userReportId The ID of the user report container element.
*/
const registerListenerEvents = (userReportId) => {
const reportContainer = document.querySelector('#' + userReportId);
const userReport = reportContainer.querySelector(SELECTORS.USER_REPORT_TABLE);
userReport.addEventListener('click', e => {
const toggle = e.target.closest(SELECTORS.CATEGORY_TOGGLE);
if (toggle) {
e.preventDefault();
toggleCategory(toggle);
}
});
};
/**
* Method that handles the category toggle action.
*
* @method toggleCategory
* @param {object} toggleElement The category toggle node that was clicked.
*/
const toggleCategory = (toggleElement) => {
const target = toggleElement.dataset.target;
const categoryId = toggleElement.dataset.categoryid;
// Whether the toggle action is collapsing the category or not.
const isCollapsing = toggleElement.getAttribute('aria-expanded') === "true";
const userReport = toggleElement.closest(SELECTORS.USER_REPORT_TABLE);
// Find all targeted 'children' rows of the toggled category.
const targetRows = userReport.querySelectorAll(target);
if (isCollapsing) {
toggleElement.setAttribute('aria-expanded', 'false');
// Update the 'data-target' of the toggle category node to make sure that when we perform another toggle action
// to expand this category we only target rows which have been hidden by this category toggle action.
toggleElement.dataset.target = `[data-hidden-by='${categoryId}']`;
} else {
toggleElement.setAttribute('aria-expanded', 'true');
// Update the 'data-target' of the toggle category node to make sure that when we perform another toggle action
// to collapse this category we only target rows which are children of this category and are not currently hidden.
toggleElement.dataset.target = `.cat_${categoryId}[data-hidden='false']`;
}
// Loop through all targeted children row elements and update the required data attributes to either hide or show
// them depending on the toggle action (collapsing or expanding).
targetRows.forEach((row) => {
if (isCollapsing) {
row.dataset.hidden = 'true';
row.dataset.hiddenBy = categoryId;
} else {
row.dataset.hidden = 'false';
row.dataset.hiddenBy = '';
}
});
// Since the user report is presented in an HTML table, rowspans are used under each category to create a visual
// hierarchy between categories and grading items. When expanding or collapsing a category we need to also update
// (subtract or add) the rowspan values associated to each parent category row to preserve the correct visual
// hierarchy in the table.
updateParentCategoryRowspans(toggleElement, targetRows.length);
};
/**
* Method that updates the rowspan value of all 'parent' category rows of a given category node.
*
* @method updateParentCategoryRowspans
* @param {object} toggleElement The category toggle node that was clicked.
* @param {int} num The number we want to add or subtract from the rowspan value of the 'parent' category row elements.
*/
const updateParentCategoryRowspans = (toggleElement, num) => {
const userReport = toggleElement.closest(SELECTORS.USER_REPORT_TABLE);
// Get the row element which contains the category toggle node.
const rowElement = toggleElement.closest('tr');
// Loop through the class list of the toggle category row element.
// The list contains classes which identify all parent categories of the toggled category.
rowElement.classList.forEach((className) => {
// Find the toggle node of the 'parent' category that is identified by the given class name.
const parentCategoryToggleElement = userReport.querySelector(`[data-target=".${className}[data-hidden='false']"`);
if (parentCategoryToggleElement) {
// Get the row element which contains the parent category toggle node.
const categoryRowElement = parentCategoryToggleElement.closest('tr');
// Find the rowspan element associated to this parent category.
const categoryRowSpanElement = categoryRowElement.nextElementSibling.querySelector('[rowspan]');
// Depending on whether the toggle action has expanded or collapsed the category, either add or
// subtract from the 'parent' category rowspan.
if (toggleElement.getAttribute('aria-expanded') === "true") {
categoryRowSpanElement.rowSpan = categoryRowSpanElement.rowSpan + num;
} else { // The category has been collapsed.
categoryRowSpanElement.rowSpan = categoryRowSpanElement.rowSpan - num;
}
}
});
};
/**
* Init method.
*
* @param {string} userReportId The ID of the user report container element.
*/
export const init = (userReportId) => {
registerListenerEvents(userReportId);
};
+58
View File
@@ -0,0 +1,58 @@
// 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/>.
/**
* Allow the user to search for groups within the user report.
*
* @module gradereport_user/group
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import GroupSearch from 'core_group/comboboxsearch/group';
import Url from 'core/url';
export default class Group extends GroupSearch {
courseID;
constructor() {
super();
// Define our standard lookups.
this.selectors = {...this.selectors,
courseid: '[data-region="courseid"]',
};
const component = document.querySelector(this.componentSelector());
this.courseID = component.querySelector(this.selectors.courseid).dataset.courseid;
}
static init() {
return new Group();
}
/**
* Build up the link that is dedicated to a particular result.
*
* @param {Number} groupID The ID of the group selected.
* @returns {string|*}
*/
selectOneLink(groupID) {
return Url.relativeUrl('/grade/report/user/index.php', {
id: this.courseID,
groupsearchvalue: this.getSearchTerm(),
group: groupID,
}, false);
}
}
+92
View File
@@ -0,0 +1,92 @@
// 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/>.
/**
* Allow the user to search for learners within the user report.
*
* @module gradereport_user/user
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import UserSearch from 'core_user/comboboxsearch/user';
import Url from 'core/url';
import {renderForPromise, replaceNodeContents} from 'core/templates';
import * as Repository from 'core_grades/searchwidget/repository';
export default class User extends UserSearch {
constructor() {
super();
}
static init() {
return new User();
}
/**
* Build the content then replace the node.
*/
async renderDropdown() {
const {html, js} = await renderForPromise('core_user/comboboxsearch/resultset', {
users: this.getMatchedResults().slice(0, 5),
hasresults: this.getMatchedResults().length > 0,
instance: this.instance,
matches: this.getDatasetSize(),
searchterm: this.getSearchTerm(),
selectall: this.selectAllResultsLink(),
});
replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);
// Remove aria-activedescendant when the available options change.
this.searchInput.removeAttribute('aria-activedescendant');
}
/**
* Build up the view all link.
*
* @returns {string|*}
*/
selectAllResultsLink() {
return Url.relativeUrl('/grade/report/user/index.php', {
id: this.courseID,
userid: 0,
searchvalue: this.getSearchTerm()
}, false);
}
/**
* Build up the link that is dedicated to a particular result.
*
* @param {Number} userID The ID of the user selected.
* @returns {string|*}
*/
selectOneLink(userID) {
return Url.relativeUrl('/grade/report/user/index.php', {
id: this.courseID,
searchvalue: this.getSearchTerm(),
userid: userID,
}, false);
}
/**
* Get the data we will be searching against in this component.
*
* @returns {Promise<*>}
*/
fetchDataset() {
// Small typing checks as sometimes groups don't exist therefore the element returns a empty string.
const gts = typeof (this.groupID) === "string" && this.groupID === '' ? 0 : this.groupID;
return Repository.userFetch(this.courseID, gts).then((r) => r.users);
}
}
@@ -0,0 +1,69 @@
<?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/>.
/**
* User report viewed event.
*
* @package gradereport_user
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradereport_user\event;
defined('MOODLE_INTERNAL') || die();
/**
* User report viewed event class.
*
* @package gradereport_user
* @since Moodle 2.8
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class grade_report_viewed extends \core\event\grade_report_viewed {
/**
* Initialise the event data.
* @return void
*/
protected function init(): void {
parent::init();
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name(): string {
return get_string('eventgradereportviewed', 'gradereport_user');
}
/**
* Custom validation.
*
* Throw \coding_exception notice in case of any problems.
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' value must be set.');
}
}
}
@@ -0,0 +1,92 @@
<?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 gradereport_user\external;
use context_course;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use stdClass;
/**
* External grade report API implementation
*
* @package gradereport_user
* @copyright 2023 Juan Leyva <juan@moodle.com>
* @category external
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_access_information extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters.
* @since Moodle 4.2
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'courseid' => new external_value(PARAM_INT, 'Course to check.'),
]
);
}
/**
* Convenience function to retrieve some permissions information for the given course grade report.
*
* @param int $courseid Course to check, empty for site.
* @return array The access information
* @since Moodle 4.2
*/
public static function execute(int $courseid): array {
$params = self::validate_parameters(self::execute_parameters(), ['courseid' => $courseid]);
$context = context_course::instance($params['courseid']);
self::validate_context($context);
return [
'canviewusergradereport' => has_capability('gradereport/user:view', $context),
'canviewmygrades' => has_capability('moodle/grade:view', $context),
'canviewallgrades' => has_capability('moodle/grade:viewall', $context),
'warnings' => [],
];
}
/**
* Returns description of method result value.
*
* @return external_single_structure.
* @since Moodle 4.2
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure(
[
'canviewusergradereport' => new external_value(PARAM_BOOL, 'Whether the user can view the user grade report.'),
'canviewmygrades' => new external_value(PARAM_BOOL, 'Whether the user can view his grades in the course.'),
'canviewallgrades' => new external_value(PARAM_BOOL, 'Whether the user can view all users grades in the course.'),
'warnings' => new external_warnings(),
]
);
}
}
+555
View File
@@ -0,0 +1,555 @@
<?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 gradereport_user\external;
use context_course;
use core_user;
use core_external\external_api;
use core_external\external_description;
use core_external\external_format_value;
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 grade_plugin_return;
use graded_users_iterator;
use moodle_exception;
use stdClass;
use gradereport_user\report\user as user_report;
require_once($CFG->dirroot.'/grade/lib.php');
/**
* External grade report API implementation
*
* @package gradereport_user
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @category external
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user extends external_api {
/**
* Validate access permissions to the report
*
* @param int $courseid the courseid
* @param int $userid the user id to retrieve data from
* @param int $groupid the group id
* @return array with the parameters cleaned and other required information
* @since Moodle 3.2
*/
protected static function check_report_access(int $courseid, int $userid, int $groupid = 0): array {
global $USER;
// Validate the parameter.
$params = self::validate_parameters(self::get_grades_table_parameters(),
[
'courseid' => $courseid,
'userid' => $userid,
'groupid' => $groupid,
]
);
// Compact/extract functions are not recommended.
$courseid = $params['courseid'];
$userid = $params['userid'];
$groupid = $params['groupid'];
// Function get_course internally throws an exception if the course doesn't exist.
$course = get_course($courseid);
$context = context_course::instance($courseid);
self::validate_context($context);
// Specific capabilities.
require_capability('gradereport/user:view', $context);
$user = null;
if (empty($userid)) {
require_capability('moodle/grade:viewall', $context);
} else {
$user = core_user::get_user($userid, '*', MUST_EXIST);
core_user::require_active_user($user);
// Check if we can view the user group (if any).
// When userid == 0, we are retrieving all the users, we'll check then if a groupid is required.
// User are always in their own group, also when they don't have groups.
if ($userid != $USER->id && !groups_user_groups_visible($course, $user->id)) {
throw new moodle_exception('notingroup');
}
}
$access = false;
if (has_capability('moodle/grade:viewall', $context)) {
// Can view all course grades.
$access = true;
} else if ($userid == $USER->id && has_capability('moodle/grade:view', $context) && $course->showgrades) {
// View own grades.
$access = true;
}
if (!$access) {
throw new moodle_exception('nopermissiontoviewgrades', 'error');
}
// User are always in their own group, also when they don't have groups.
if ($userid != $USER->id) {
if (!empty($groupid)) {
// Determine if the group is visible to user.
if (!groups_group_visible($groupid, $course)) {
throw new moodle_exception('notingroup');
}
} else {
// Check to see if groups are being used here.
if ($groupmode = groups_get_course_groupmode($course)) {
$groupid = groups_get_course_group($course);
// Determine if the group is visible to the user (this is particularly for group 0).
if (!groups_group_visible($groupid, $course)) {
throw new moodle_exception('notingroup');
}
} else {
$groupid = 0;
}
}
}
return [$params, $course, $context, $user, $groupid];
}
/**
* Get the report data
* @param stdClass $course course object
* @param stdClass $context context object
* @param null|stdClass $user user object (it can be null for all the users)
* @param int $userid the user to retrieve data from, 0 for all
* @param int $groupid the group id to filter
* @param bool $tabledata whether to get the table data (true) or the gradeitemdata
* @return array data and possible warnings
* @since Moodle 3.2
*/
protected static function get_report_data(
stdClass $course,
stdClass $context,
?stdClass $user,
int $userid,
int $groupid,
bool $tabledata = true
): array {
global $CFG;
$warnings = [];
// Require files here to save some memory in case validation fails.
require_once($CFG->dirroot . '/group/lib.php');
require_once($CFG->libdir . '/gradelib.php');
require_once($CFG->dirroot . '/grade/lib.php');
require_once($CFG->dirroot . '/grade/report/user/lib.php');
// Force regrade to update items marked as 'needupdate'.
grade_regrade_final_grades($course->id);
$gpr = new grade_plugin_return(
[
'type' => 'report',
'plugin' => 'user',
'courseid' => $course->id,
'courseidnumber' => $course->idnumber,
'userid' => $userid
]
);
$reportdata = [];
// Just one user.
if ($user) {
$report = new user_report($course->id, $gpr, $context, $userid);
$report->fill_table();
$gradeuserdata = [
'courseid' => $course->id,
'courseidnumber' => $course->idnumber,
'userid' => $user->id,
'userfullname' => fullname($user),
'useridnumber' => $user->idnumber,
'maxdepth' => $report->maxdepth,
];
if ($tabledata) {
$gradeuserdata['tabledata'] = $report->tabledata;
} else {
$gradeuserdata['gradeitems'] = $report->gradeitemsdata;
}
$reportdata[] = $gradeuserdata;
} else {
$defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
$showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
$showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $context);
$gui = new graded_users_iterator($course, null, $groupid);
$gui->require_active_enrolment($showonlyactiveenrol);
$gui->init();
while ($userdata = $gui->next_user()) {
$currentuser = $userdata->user;
$report = new user_report($course->id, $gpr, $context, $currentuser->id);
$report->fill_table();
$gradeuserdata = [
'courseid' => $course->id,
'courseidnumber' => $course->idnumber,
'userid' => $currentuser->id,
'userfullname' => fullname($currentuser),
'useridnumber' => $currentuser->idnumber,
'maxdepth' => $report->maxdepth,
];
if ($tabledata) {
$gradeuserdata['tabledata'] = $report->tabledata;
} else {
$gradeuserdata['gradeitems'] = $report->gradeitemsdata;
}
$reportdata[] = $gradeuserdata;
}
$gui->close();
}
return [$reportdata, $warnings];
}
/**
* Describes the parameters for get_grades_table.
*
* @return external_function_parameters
* @since Moodle 2.9
*/
public static function get_grades_table_parameters(): external_function_parameters {
return new external_function_parameters (
[
'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED),
'userid' => new external_value(PARAM_INT, 'Return grades only for this user (optional)', VALUE_DEFAULT, 0),
'groupid' => new external_value(PARAM_INT, 'Get users from this group only', VALUE_DEFAULT, 0)
]
);
}
/**
* Returns a list of grades tables for users in a course.
*
* @param int $courseid Course Id
* @param int $userid Only this user (optional)
* @param int $groupid Get users from this group only
*
* @return array the grades tables
* @since Moodle 2.9
*/
public static function get_grades_table(int $courseid, int $userid = 0, int $groupid = 0): array {
list($params, $course, $context, $user, $groupid) = self::check_report_access($courseid, $userid, $groupid);
$userid = $params['userid'];
// We pass userid because it can be still 0.
list($tables, $warnings) = self::get_report_data($course, $context, $user, $userid, $groupid);
return [
'tables' => $tables,
'warnings' => $warnings
];
}
/**
* Creates a table column structure
*
* @return array
* @since Moodle 2.9
*/
private static function grades_table_column(): array {
return [
'class' => new external_value(PARAM_RAW, 'class'),
'content' => new external_value(PARAM_RAW, 'cell content'),
'headers' => new external_value(PARAM_RAW, 'headers')
];
}
/**
* Describes tget_grades_table return value.
*
* @return external_single_structure
* @since Moodle 2.9
*/
public static function get_grades_table_returns(): external_single_structure {
return new external_single_structure(
[
'tables' => new external_multiple_structure(
new external_single_structure(
[
'courseid' => new external_value(PARAM_INT, 'course id'),
'userid' => new external_value(PARAM_INT, 'user id'),
'userfullname' => new external_value(PARAM_TEXT, 'user fullname'),
'maxdepth' => new external_value(PARAM_INT, 'table max depth (needed for printing it)'),
'tabledata' => new external_multiple_structure(
new external_single_structure(
[
'itemname' => new external_single_structure(
[
'class' => new external_value(PARAM_RAW, 'class'),
'colspan' => new external_value(PARAM_INT, 'col span'),
'content' => new external_value(PARAM_RAW, 'cell content'),
'id' => new external_value(PARAM_ALPHANUMEXT, 'id')
], 'The item returned data', VALUE_OPTIONAL
),
'leader' => new external_single_structure(
[
'class' => new external_value(PARAM_RAW, 'class'),
'rowspan' => new external_value(PARAM_INT, 'row span')
], 'The item returned data', VALUE_OPTIONAL
),
'weight' => new external_single_structure(
self::grades_table_column(), 'weight column', VALUE_OPTIONAL
),
'grade' => new external_single_structure(
self::grades_table_column(), 'grade column', VALUE_OPTIONAL
),
'range' => new external_single_structure(
self::grades_table_column(), 'range column', VALUE_OPTIONAL
),
'percentage' => new external_single_structure(
self::grades_table_column(), 'percentage column', VALUE_OPTIONAL
),
'lettergrade' => new external_single_structure(
self::grades_table_column(), 'lettergrade column', VALUE_OPTIONAL
),
'rank' => new external_single_structure(
self::grades_table_column(), 'rank column', VALUE_OPTIONAL
),
'average' => new external_single_structure(
self::grades_table_column(), 'average column', VALUE_OPTIONAL
),
'feedback' => new external_single_structure(
self::grades_table_column(), 'feedback column', VALUE_OPTIONAL
),
'contributiontocoursetotal' => new external_single_structure(
self::grades_table_column(), 'contributiontocoursetotal column', VALUE_OPTIONAL
),
'parentcategories' => new external_multiple_structure(
new external_value(PARAM_INT, 'Parent grade category ID.')
),
], 'table'
)
)
]
)
),
'warnings' => new external_warnings()
]
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.9
*/
public static function view_grade_report_parameters(): external_function_parameters {
return new external_function_parameters(
[
'courseid' => new external_value(PARAM_INT, 'id of the course'),
'userid' => new external_value(PARAM_INT, 'id of the user, 0 means current user', VALUE_DEFAULT, 0),
]
);
}
/**
* Trigger the user report events, do the same that the web interface view of the report
*
* @param int $courseid id of course
* @param int $userid id of the user the report belongs to
* @return array of warnings and status result
* @since Moodle 2.9
* @throws moodle_exception
*/
public static function view_grade_report(int $courseid, int $userid = 0): array {
global $CFG, $USER;
require_once($CFG->dirroot . "/grade/lib.php");
require_once($CFG->dirroot . "/grade/report/user/lib.php");
$params = self::validate_parameters(self::view_grade_report_parameters(),
[
'courseid' => $courseid,
'userid' => $userid
]);
$warnings = [];
$course = get_course($params['courseid']);
$context = context_course::instance($course->id);
self::validate_context($context);
$userid = $params['userid'];
if (empty($userid)) {
$userid = $USER->id;
} else {
$user = core_user::get_user($userid, '*', MUST_EXIST);
core_user::require_active_user($user);
}
$access = false;
if (has_capability('moodle/grade:viewall', $context)) {
// Can view all course grades (any user).
$access = true;
} else if ($userid == $USER->id && has_capability('moodle/grade:view', $context) && $course->showgrades) {
// View own grades.
$access = true;
}
if (!$access) {
throw new moodle_exception('nopermissiontoviewgrades', 'error');
}
// Create a report instance. We don't need the gpr second parameter.
$report = new user_report($course->id, null, $context, $userid);
$report->viewed();
return [
'status' => true,
'warnings' => $warnings
];
}
/**
* Returns description of method result value
*
* @return external_description
* @since Moodle 2.9
*/
public static function view_grade_report_returns(): external_description {
return new external_single_structure(
[
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
]
);
}
/**
* Describes the parameters for get_grade_items.
*
* @return external_function_parameters
* @since Moodle 3.2
*/
public static function get_grade_items_parameters(): external_function_parameters {
return self::get_grades_table_parameters();
}
/**
* Returns the complete list of grade items for users in a course.
*
* @param int $courseid Course Id
* @param int $userid Only this user (optional)
* @param int $groupid Get users from this group only
*
* @return array the grades tables
* @since Moodle 3.2
*/
public static function get_grade_items(int $courseid, int $userid = 0, int $groupid = 0): array {
list($params, $course, $context, $user, $groupid) = self::check_report_access($courseid, $userid, $groupid);
$userid = $params['userid'];
// We pass userid because it can be still 0.
list($gradeitems, $warnings) = self::get_report_data($course, $context, $user, $userid, $groupid, false);
foreach ($gradeitems as $gradeitem) {
if (isset($gradeitem['feedback']) && isset($gradeitem['feedbackformat'])) {
list($gradeitem['feedback'], $gradeitem['feedbackformat']) =
\core_external\util::format_text($gradeitem['feedback'], $gradeitem['feedbackformat'], $context->id);
}
}
return [
'usergrades' => $gradeitems,
'warnings' => $warnings
];
}
/**
* Describes tget_grade_items return value.
*
* @return external_single_structure
* @since Moodle 3.2
*/
public static function get_grade_items_returns(): external_single_structure {
return new external_single_structure(
[
'usergrades' => new external_multiple_structure(
new external_single_structure(
[
'courseid' => new external_value(PARAM_INT, 'course id'),
'courseidnumber' => new external_value(PARAM_TEXT, 'course idnumber'),
'userid' => new external_value(PARAM_INT, 'user id'),
'userfullname' => new external_value(PARAM_TEXT, 'user fullname'),
'useridnumber' => new external_value(
core_user::get_property_type('idnumber'), 'user idnumber'),
'maxdepth' => new external_value(PARAM_INT, 'table max depth (needed for printing it)'),
'gradeitems' => new external_multiple_structure(
new external_single_structure(
[
'id' => new external_value(PARAM_INT, 'Grade item id'),
'itemname' => new external_value(PARAM_RAW, 'Grade item name'),
'itemtype' => new external_value(PARAM_ALPHA, 'Grade item type'),
'itemmodule' => new external_value(PARAM_PLUGIN, 'Grade item module'),
'iteminstance' => new external_value(PARAM_INT, 'Grade item instance'),
'itemnumber' => new external_value(PARAM_INT, 'Grade item item number'),
'idnumber' => new external_value(PARAM_TEXT, 'Grade item idnumber'),
'categoryid' => new external_value(PARAM_INT, 'Grade item category id'),
'outcomeid' => new external_value(PARAM_INT, 'Outcome id'),
'scaleid' => new external_value(PARAM_INT, 'Scale id'),
'locked' => new external_value(PARAM_BOOL, 'Grade item for user locked?', VALUE_OPTIONAL),
'cmid' => new external_value(PARAM_INT, 'Course module id (if type mod)', VALUE_OPTIONAL),
'weightraw' => new external_value(PARAM_FLOAT, 'Weight raw', VALUE_OPTIONAL),
'weightformatted' => new external_value(PARAM_NOTAGS, 'Weight', VALUE_OPTIONAL),
'status' => new external_value(PARAM_ALPHA, 'Status', VALUE_OPTIONAL),
'graderaw' => new external_value(PARAM_FLOAT, 'Grade raw', VALUE_OPTIONAL),
'gradedatesubmitted' => new external_value(PARAM_INT, 'Grade submit date', VALUE_OPTIONAL),
'gradedategraded' => new external_value(PARAM_INT, 'Grade graded date', VALUE_OPTIONAL),
'gradehiddenbydate' => new external_value(PARAM_BOOL, 'Grade hidden by date?', VALUE_OPTIONAL),
'gradeneedsupdate' => new external_value(PARAM_BOOL, 'Grade needs update?', VALUE_OPTIONAL),
'gradeishidden' => new external_value(PARAM_BOOL, 'Grade is hidden?', VALUE_OPTIONAL),
'gradeislocked' => new external_value(PARAM_BOOL, 'Grade is locked?', VALUE_OPTIONAL),
'gradeisoverridden' => new external_value(PARAM_BOOL, 'Grade overridden?', VALUE_OPTIONAL),
'gradeformatted' => new external_value(PARAM_RAW, 'The grade formatted', VALUE_OPTIONAL),
'grademin' => new external_value(PARAM_FLOAT, 'Grade min', VALUE_OPTIONAL),
'grademax' => new external_value(PARAM_FLOAT, 'Grade max', VALUE_OPTIONAL),
'rangeformatted' => new external_value(PARAM_NOTAGS, 'Range formatted', VALUE_OPTIONAL),
'percentageformatted' => new external_value(PARAM_NOTAGS, 'Percentage', VALUE_OPTIONAL),
'lettergradeformatted' => new external_value(PARAM_NOTAGS, 'Letter grade', VALUE_OPTIONAL),
'rank' => new external_value(PARAM_INT, 'Rank in the course', VALUE_OPTIONAL),
'numusers' => new external_value(PARAM_INT, 'Num users in course', VALUE_OPTIONAL),
'averageformatted' => new external_value(PARAM_NOTAGS, 'Grade average', VALUE_OPTIONAL),
'feedback' => new external_value(PARAM_RAW, 'Grade feedback', VALUE_OPTIONAL),
'feedbackformat' => new external_format_value('feedback', VALUE_OPTIONAL),
], 'Grade items'
)
)
]
)
),
'warnings' => new external_warnings()
]
);
}
}
@@ -0,0 +1,98 @@
<?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 gradereport_user\output;
use moodle_url;
use core_grades\output\general_action_bar;
/**
* Renderable class for the action bar elements in the user report page.
*
* @package gradereport_user
* @copyright 2022 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class action_bar extends \core_grades\output\action_bar {
/** @var int|null $userid The user ID. */
protected $userid;
/** @var int $userview The user report view mode. */
protected $userview;
/** @var int|null $currentgroupid The user report view mode. */
protected $currentgroupid;
/**
* The class constructor.
*
* @param \context $context The context object.
* @param int $userview The user report view mode.
* @param int|null $userid The user ID or 0 if displaying all users.
* @param int|null $currentgroupid The ID of the current group.
*/
public function __construct(\context $context, int $userview, ?int $userid = null, ?int $currentgroupid = null) {
parent::__construct($context);
$this->userview = $userview;
$this->userid = $userid;
$this->currentgroupid = $currentgroupid;
}
/**
* Returns the template for the action bar.
*
* @return string
*/
public function get_template(): string {
return 'gradereport_user/action_bar';
}
/**
* Export the data for the mustache template.
*
* @param \renderer_base $output renderer to be used to render the action bar elements.
* @return array
*/
public function export_for_template(\renderer_base $output): array {
global $PAGE, $USER;
// If in the course context, we should display the general navigation selector in gradebook.
$courseid = $this->context->instanceid;
// Get the data used to output the general navigation selector.
$generalnavselector = new general_action_bar($this->context,
new moodle_url('/grade/report/user/index.php', ['id' => $courseid]), 'gradereport', 'user');
$data = $generalnavselector->export_for_template($output);
// If the user has the capability to view all grades, display the group selector (if applicable), the user selector
// and the view mode selector (if applicable).
if (has_capability('moodle/grade:viewall', $this->context)) {
$userreportrenderer = $PAGE->get_renderer('gradereport_user');
$data['groupselector'] = $PAGE->get_renderer('core_grades')->group_selector(get_course($courseid));
$data['userselector'] = [
'courseid' => $courseid,
'content' => $userreportrenderer->users_selector(get_course($courseid), $this->userid, $this->currentgroupid)
];
// Do not output the 'view mode' selector when in zero state or when the current user is viewing its own report.
if (!is_null($this->userid) && $USER->id != $this->userid) {
$data['viewasselector'] = $userreportrenderer->view_mode_selector($this->userid, $this->userview, $courseid);
}
}
return $data;
}
}
@@ -0,0 +1,76 @@
<?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 Subsystem implementation for gradereport_user.
*
* @package gradereport_user
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradereport_user\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\writer;
require_once $CFG->libdir.'/grade/constants.php';
/**
* Privacy Subsystem for gradereport_user implementing null_provider.
*
* @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_privacy\local\request\user_preference_provider {
/**
* Returns meta data about this system.
*
* @param collection $itemcollection The initialised item collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $items): collection {
// User preferences (shared between different courses).
$items->add_user_preference('gradereport_user_view_user', 'privacy:metadata:preference:gradereport_user_view_user');
return $items;
}
/**
* 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('gradereport_user_view_user', null, $userid);
if ($prefvalue !== null) {
$transformedvalue = transform::yesno($prefvalue);
writer::export_user_preference(
'gradereport_user',
'gradereport_user_view_user',
$transformedvalue,
get_string('privacy:metadata:preference:gradereport_user_view_user', 'gradereport_user')
);
}
}
}
File diff suppressed because it is too large Load Diff
+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/>.
/**
* Defines capabilities for the user report
*
* @package gradereport_user
* @copyright 2007 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = [
'gradereport/user:view' => [
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => [
'student' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
]
],
];
+59
View File
@@ -0,0 +1,59 @@
<?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/>.
/**
* User grade report external functions and service definitions.
*
* @package gradereport_user
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$functions = [
'gradereport_user_get_grades_table' => [
'classname' => 'gradereport_user\\external\\user',
'methodname' => 'get_grades_table',
'description' => 'Get the user/s report grades table for a course',
'type' => 'read',
'capabilities' => 'gradereport/user:view',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
'gradereport_user_view_grade_report' => [
'classname' => 'gradereport_user\\external\\user',
'methodname' => 'view_grade_report',
'description' => 'Trigger the report view event',
'type' => 'write',
'capabilities' => 'gradereport/user:view',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
'gradereport_user_get_grade_items' => [
'classname' => 'gradereport_user\\external\\user',
'methodname' => 'get_grade_items',
'description' => 'Returns the complete list of grade items for users in a course',
'type' => 'read',
'capabilities' => 'gradereport/user:view',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
'gradereport_user_get_access_information' => [
'classname' => 'gradereport_user\external\get_access_information',
'methodname' => 'execute',
'description' => 'Returns user access information for the user grade report.',
'type' => 'read',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
];
+43
View File
@@ -0,0 +1,43 @@
<?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/>.
/**
* Gradereport user plugin upgrade code
*
* @package gradereport_user
* @copyright 2014 Zachary Durber
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_gradereport_user_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;
}
+214
View File
@@ -0,0 +1,214 @@
<?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 gradebook user report
*
* @package gradereport_user
* @copyright 2007 Moodle Pty Ltd (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once '../../../config.php';
require_once $CFG->libdir.'/gradelib.php';
require_once $CFG->dirroot.'/grade/lib.php';
require_once $CFG->dirroot.'/grade/report/user/lib.php';
$courseid = required_param('id', PARAM_INT);
$userid = optional_param('userid', null, PARAM_INT);
$userview = optional_param('userview', 0, PARAM_INT);
$PAGE->set_url(new moodle_url('/grade/report/user/index.php', ['id' => $courseid]));
if ($userview == 0) {
$userview = get_user_preferences('gradereport_user_view_user', GRADE_REPORT_USER_VIEW_USER);
} else {
set_user_preference('gradereport_user_view_user', $userview);
}
// Basic access checks.
if (!$course = $DB->get_record('course', ['id' => $courseid])) {
throw new \moodle_exception('invalidcourseid');
}
require_login($course);
$PAGE->set_pagelayout('report');
$context = context_course::instance($course->id);
require_capability('gradereport/user:view', $context);
if ($userid === 0) {
require_capability('moodle/grade:viewall', $context);
} else if ($userid) {
if (!$DB->get_record('user', ['id' => $userid, 'deleted' => 0]) || isguestuser($userid)) {
throw new \moodle_exception('invaliduser');
}
}
$access = false;
if (has_capability('moodle/grade:viewall', $context)) {
// User can view all course grades.
$access = true;
} else if (($userid == $USER->id || is_null($userid)) && has_capability('moodle/grade:view', $context) && $course->showgrades) {
// User can view own grades.
$access = true;
} else if (has_capability('moodle/grade:viewall', context_user::instance($userid)) && $course->showgrades) {
// User can view grades of this user, The user is an parent most probably.
$access = true;
}
if (!$access) {
// The user has no access to grades.
throw new \moodle_exception('nopermissiontoviewgrades', 'error', $CFG->wwwroot.'/course/view.php?id='.$courseid);
}
// Initialise the grade tracking object.
$gpr = new grade_plugin_return(['type' => 'report', 'plugin' => 'user', 'courseid' => $courseid, 'userid' => $userid]);
// Infer the users previously selected report via session tracking.
if (!isset($USER->grade_last_report)) {
$USER->grade_last_report = [];
}
$USER->grade_last_report[$course->id] = 'user';
// First make sure we have proper final grades.
grade_regrade_final_grades_if_required($course);
$gradesrenderer = $PAGE->get_renderer('core_grades');
// Teachers will see all student reports.
if (has_capability('moodle/grade:viewall', $context)) {
// Verify if we are using groups or not.
$groupmode = groups_get_course_groupmode($course);
$currentgroup = $gpr->groupid;
// Conditionally add the group JS if we have groups enabled.
if ($groupmode) {
$PAGE->requires->js_call_amd('gradereport_user/group', 'init');
}
// To make some other functions work better later.
if (!$currentgroup) {
$currentgroup = null;
}
$isseparategroups = ($course->groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context));
if ($isseparategroups && (!$currentgroup)) {
// No separate group access, The user can view only themselves.
$userid = $USER->id;
}
// If there is a stored (last viewed) user in a session variable, bypass the user select zero state and display the
// report for that user.
$lastvieweduserid = $SESSION->gradereport_user["useritem-{$context->id}"] ?? null;
if (is_null($userid) && !is_null($lastvieweduserid)) {
$userid = $lastvieweduserid;
}
$gradableusers = grade_report::get_gradable_users($courseid, $currentgroup);
// Validate whether the requested user is a valid gradable user in this course. If, not display the user select
// zero state.
if (empty($gradableusers) || ($userid && !array_key_exists($userid, $gradableusers))) {
$userid = null;
}
$defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
$showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
$showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $context);
if ($userview == GRADE_REPORT_USER_VIEW_USER) {
$viewasuser = true;
} else {
$viewasuser = false;
}
$gui = new graded_users_iterator($course, null, $currentgroup);
$gui->require_active_enrolment($showonlyactiveenrol);
$gui->init();
if (is_null($userid)) { // Zero state.
$actionbar = new \gradereport_user\output\action_bar($context, $userview, null, $currentgroup);
// Print header.
print_grade_page_head($courseid, 'report', 'user', false, false, null, true,
null, null, null, $actionbar);
if (empty($gradableusers)) { // There are no available gradable users, display a notification.
$message = $currentgroup ? get_string('nostudentsingroup') : get_string('nostudentsyet');
echo $OUTPUT->notification($message, 'warning', false);
} else { // Otherwise, display the zero state template.
$report = new gradereport_user\report\user($courseid, $gpr, $context, $USER->id, $viewasuser);
echo $report->output_report_zerostate();
}
} else if ($userid == 0) { // Show all reports.
// Store the id of the current user item in a session variable which represents the last viewed item.
$SESSION->gradereport_user["useritem-{$context->id}"] = $userid;
$actionbar = new \gradereport_user\output\action_bar($context, $userview, 0, $currentgroup);
print_grade_page_head($courseid, 'report', 'user', false, false, null, true,
null, null, null, $actionbar);
while ($userdata = $gui->next_user()) {
$user = $userdata->user;
$report = new gradereport_user\report\user($courseid, $gpr, $context, $user->id, $viewasuser);
$userheading = $gradesrenderer->user_heading($report->user, $courseid, false);
echo $userheading;
if ($report->fill_table()) {
echo $report->print_table(true);
}
}
} else { // Show one user's report.
// Store the id of the current user item in a session variable which represents the last viewed item.
$SESSION->gradereport_user["useritem-{$context->id}"] = $userid;
$report = new gradereport_user\report\user($courseid, $gpr, $context, $userid, $viewasuser);
$actionbar = new \gradereport_user\output\action_bar($context, $userview, $report->user->id, $currentgroup);
print_grade_page_head($courseid, 'report', 'user', false, false, false, true, null, null, $report->user, $actionbar);
if ($currentgroup && !groups_is_member($currentgroup, $userid)) {
echo $OUTPUT->notification(get_string('groupusernotmember', 'error'));
} else {
if ($report->fill_table()) {
echo $report->print_table(true);
}
}
$userreportrenderer = $PAGE->get_renderer('gradereport_user');
// Render the user report (previous/next) navigation in a sticky footer.
$stickyfooter = new core\output\sticky_footer($userreportrenderer->user_navigation($gui, $userid, $courseid));
echo $OUTPUT->render($stickyfooter);
}
$gui->close();
} else {
// Students will see just their own report.
// Create a report instance.
$report = new gradereport_user\report\user($courseid, $gpr, $context, $userid ?? $USER->id);
// Print the page.
print_grade_page_head($courseid, 'report', 'user', false, false, false, true, null, null, $report->user);
if ($report->fill_table()) {
echo $report->print_table(true);
}
}
if (isset($report)) {
// Trigger report viewed event.
$report->viewed();
}
echo $OUTPUT->footer();
@@ -0,0 +1,37 @@
<?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 component 'gradereport_user', language 'en'
*
* @package gradereport_user
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['allusersnum'] = 'All users ({$a})';
$string['eventgradereportviewed'] = 'Grade user report viewed';
$string['gotonextreport'] = 'Go to next user report';
$string['gotopreviousreport'] = 'Go to previous user report';
$string['pluginname'] = 'User report';
$string['user:view'] = 'View user report';
$string['userreportdesc'] = 'User reports include a users grades, feedback and the course total.';
$string['userreports'] = 'Search for a user to view their report';
$string['privacy:metadata:preference:gradereport_user_view_user'] = 'Whether to view report as current user or another user in the gradebook reports';
$string['tablesummary'] = 'The table is arranged as a list of graded items including categories of graded items. When items are in a category they will be indicated as such.';
$string['userreport_user'] = 'User report';
+280
View File
@@ -0,0 +1,280 @@
<?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/>.
/**
* Definition of the grade_user_report class is defined
*
* @package gradereport_user
* @copyright 2007 Nicolas Connault
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_user\output\myprofile\tree;
require_once($CFG->dirroot . '/grade/report/lib.php');
require_once($CFG->libdir.'/tablelib.php');
define("GRADE_REPORT_USER_HIDE_HIDDEN", 0);
define("GRADE_REPORT_USER_HIDE_UNTIL", 1);
define("GRADE_REPORT_USER_SHOW_HIDDEN", 2);
define("GRADE_REPORT_USER_VIEW_SELF", 1);
define("GRADE_REPORT_USER_VIEW_USER", 2);
function grade_report_user_settings_definition(&$mform) {
global $CFG;
$options = [
-1 => get_string('default', 'grades'),
0 => get_string('hide'),
1 => get_string('show')
];
if (empty($CFG->grade_report_user_showrank)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[1]);
}
$mform->addElement('select', 'report_user_showrank', get_string('showrank', 'grades'), $options);
$mform->addHelpButton('report_user_showrank', 'showrank', 'grades');
if (empty($CFG->grade_report_user_showpercentage)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[1]);
}
$mform->addElement('select', 'report_user_showpercentage', get_string('showpercentage', 'grades'), $options);
$mform->addHelpButton('report_user_showpercentage', 'showpercentage', 'grades');
if (empty($CFG->grade_report_user_showgrade)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[1]);
}
$mform->addElement('select', 'report_user_showgrade', get_string('showgrade', 'grades'), $options);
if (empty($CFG->grade_report_user_showfeedback)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[1]);
}
$mform->addElement('select', 'report_user_showfeedback', get_string('showfeedback', 'grades'), $options);
if (empty($CFG->grade_report_user_showweight)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[1]);
}
$mform->addElement('select', 'report_user_showweight', get_string('showweight', 'grades'), $options);
if (empty($CFG->grade_report_user_showaverage)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[1]);
}
$mform->addElement('select', 'report_user_showaverage', get_string('showaverage', 'grades'), $options);
$mform->addHelpButton('report_user_showaverage', 'showaverage', 'grades');
if (empty($CFG->grade_report_user_showlettergrade)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[1]);
}
$mform->addElement('select', 'report_user_showlettergrade', get_string('showlettergrade', 'grades'), $options);
if (empty($CFG->grade_report_user_showcontributiontocoursetotal)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[$CFG->grade_report_user_showcontributiontocoursetotal]);
}
$mform->addElement('select', 'report_user_showcontributiontocoursetotal', get_string('showcontributiontocoursetotal', 'grades'), $options);
$mform->addHelpButton('report_user_showcontributiontocoursetotal', 'showcontributiontocoursetotal', 'grades');
if (empty($CFG->grade_report_user_showrange)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[1]);
}
$mform->addElement('select', 'report_user_showrange', get_string('showrange', 'grades'), $options);
$options = [
0 => 0,
1 => 1,
2 => 2,
3 => 3,
4 => 4,
5 => 5
];
if (!empty($CFG->grade_report_user_rangedecimals)) {
$options[-1] = $options[$CFG->grade_report_user_rangedecimals];
}
$mform->addElement('select', 'report_user_rangedecimals', get_string('rangedecimals', 'grades'), $options);
$options = [
-1 => get_string('default', 'grades'),
0 => get_string('shownohidden', 'grades'),
1 => get_string('showhiddenuntilonly', 'grades'),
2 => get_string('showallhidden', 'grades')
];
if (empty($CFG->grade_report_user_showhiddenitems)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[$CFG->grade_report_user_showhiddenitems]);
}
$mform->addElement('select', 'report_user_showhiddenitems', get_string('showhiddenitems', 'grades'), $options);
$mform->addHelpButton('report_user_showhiddenitems', 'showhiddenitems', 'grades');
$options = [
-1 => get_string('default', 'grades'),
GRADE_REPORT_HIDE_TOTAL_IF_CONTAINS_HIDDEN => get_string('hide'),
GRADE_REPORT_SHOW_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowexhiddenitems', 'grades'),
GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowinchiddenitems', 'grades')
];
if (empty($CFG->grade_report_user_showtotalsifcontainhidden)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[$CFG->grade_report_user_showtotalsifcontainhidden]);
}
$mform->addElement('select', 'report_user_showtotalsifcontainhidden', get_string('hidetotalifhiddenitems', 'grades'), $options);
$mform->addHelpButton('report_user_showtotalsifcontainhidden', 'hidetotalifhiddenitems', 'grades');
}
/**
* Profile report callback.
*
* @param object $course The course.
* @param object $user The user.
* @param boolean $viewasuser True when we are viewing this as the targetted user sees it.
*/
function grade_report_user_profilereport(object $course, object $user, bool $viewasuser = false) {
if (!empty($course->showgrades)) {
$context = context_course::instance($course->id);
// Fetch the return tracking object.
$gpr = new grade_plugin_return(
['type' => 'report', 'plugin' => 'user', 'courseid' => $course->id, 'userid' => $user->id]
);
// Create a report instance.
$report = new gradereport_user\report\user($course->id, $gpr, $context, $user->id, $viewasuser);
// Print the page.
// A css fix to share styles with real report page.
echo '<div class="grade-report-user">';
if ($report->fill_table()) {
echo $report->print_table(true);
}
echo '</div>';
}
}
/**
* Add nodes to myprofile page.
*
* @param tree $tree Tree object
* @param stdClass $user user object
* @param bool $iscurrentuser
* @param null|stdClass $course Course object
*/
function gradereport_user_myprofile_navigation(tree $tree, stdClass $user, bool $iscurrentuser, ?stdClass $course) {
if (empty($course)) {
// We want to display these reports under the site context.
$course = get_fast_modinfo(SITEID)->get_course();
}
$usercontext = context_user::instance($user->id);
$anyreport = has_capability('moodle/user:viewuseractivitiesreport', $usercontext);
// Start capability checks.
if ($anyreport || $iscurrentuser) {
// Add grade hardcoded grade report if necessary.
$gradeaccess = false;
$coursecontext = context_course::instance($course->id);
if (has_capability('moodle/grade:viewall', $coursecontext)) {
// Can view all course grades.
$gradeaccess = true;
} else if ($course->showgrades) {
if ($iscurrentuser && has_capability('moodle/grade:view', $coursecontext)) {
// Can view own grades.
$gradeaccess = true;
} else if (has_capability('moodle/grade:viewall', $usercontext)) {
// Can view grades of this user - parent most probably.
$gradeaccess = true;
} else if ($anyreport) {
// Can view grades of this user - parent most probably.
$gradeaccess = true;
}
}
if ($gradeaccess) {
$url = new moodle_url('/course/user.php', array('mode' => 'grade', 'id' => $course->id, 'user' => $user->id));
$node = new core_user\output\myprofile\node('reports', 'grade', get_string('grades'), null, $url);
$tree->add_node($node);
}
}
}
/**
* Returns link to user report for the current element
*
* @param context_course $context Course context
* @param int $courseid Course ID
* @param array $element An array representing an element in the grade_tree
* @param grade_plugin_return $gpr A grade_plugin_return object
* @param string $mode Mode - gradeitem or user
* @param ?stdClass $templatecontext Template context
* @return stdClass|null
*/
function gradereport_user_get_report_link(context_course $context, int $courseid, array $element,
grade_plugin_return $gpr, string $mode, ?stdClass $templatecontext): ?stdClass {
global $CFG;
if ($mode == 'user') {
$reportstring = get_string('userreport_' . $mode, 'gradereport_user');
if (!isset($templatecontext)) {
$templatecontext = new stdClass();
}
// FIXME: MDL-52678 This get_capability_info is hacky and we should have an API for inserting grade row links instead.
$canseeuserreport = false;
if (get_capability_info('gradereport/' . $CFG->grade_profilereport . ':view')) {
$canseeuserreport = has_capability('gradereport/' . $CFG->grade_profilereport . ':view', $context);
}
if ($canseeuserreport) {
$url = new moodle_url('/grade/report/' . $CFG->grade_profilereport . '/index.php',
['userid' => $element['userid'], 'id' => $courseid]);
$gpr->add_url_params($url);
$templatecontext->reporturl1 = html_writer::link($url, $reportstring,
['class' => 'dropdown-item', 'aria-label' => $reportstring, 'role' => 'menuitem']);
return $templatecontext;
}
}
return null;
}
+9
View File
@@ -0,0 +1,9 @@
<svg width="128" height="119" viewBox="0 0 128 119" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMid meet">
<path d="M54.48 108.96C84.5685 108.96 108.96 84.5685 108.96 54.48C108.96 24.3915 84.5685 0 54.48 0C24.3915 0 0 24.3915 0 54.48C0 84.5685 24.3915 108.96 54.48 108.96Z" fill="#C4E5FF"/>
<path d="M73.6898 29.0098H33.5398C30.4856 29.0098 28.0098 31.4856 28.0098 34.5398V34.5498C28.0098 37.6039 30.4856 40.0798 33.5398 40.0798H73.6898C76.7439 40.0798 79.2198 37.6039 79.2198 34.5498V34.5398C79.2198 31.4856 76.7439 29.0098 73.6898 29.0098Z" fill="#85CAFF"/>
<path d="M73.6898 72.4297H33.5398C30.4856 72.4297 28.0098 74.9056 28.0098 77.9597V77.9697C28.0098 81.0238 30.4856 83.4997 33.5398 83.4997H73.6898C76.7439 83.4997 79.2198 81.0238 79.2198 77.9697V77.9597C79.2198 74.9056 76.7439 72.4297 73.6898 72.4297Z" fill="#85CAFF"/>
<path d="M96.6497 47.9404H36.3297C31.7347 47.9404 28.0098 51.6632 28.0098 56.2554C28.0098 60.8477 31.7347 64.5704 36.3297 64.5704H96.6497C101.245 64.5704 104.97 60.8477 104.97 56.2554C104.97 51.6632 101.245 47.9404 96.6497 47.9404Z" fill="#85CAFF" stroke="#0056B2" stroke-width="2" stroke-miterlimit="10"/>
<path d="M40.9306 59.9601C40.9276 59.9169 40.9276 59.8734 40.9306 59.8301V59.7101L40.8706 59.5801L40.8106 59.4702C40.7654 59.4004 40.7116 59.3366 40.6506 59.2802L40.5606 59.2001L40.3607 59.0601L40.2506 59.0001C39.9335 58.8481 39.597 58.7404 39.2506 58.6801C38.9327 58.6107 38.6324 58.4774 38.3676 58.2883C38.1029 58.0992 37.8794 57.8583 37.7107 57.5801C37.6981 57.4198 37.7211 57.2586 37.7782 57.1082C37.8352 56.9577 37.9248 56.8218 38.0406 56.7101L38.1306 56.6002C38.3931 56.251 38.565 55.8421 38.6306 55.4102C38.6306 55.4102 38.6306 55.3401 38.6306 55.3101C38.7701 54.6878 38.7701 54.0424 38.6306 53.4201C38.5121 53.014 38.2743 52.6528 37.948 52.3835C37.6216 52.1143 37.2219 51.9494 36.8006 51.9102H36.5106H36.2207H36.1606C35.7394 51.9494 35.3396 52.1143 35.0133 52.3835C34.6869 52.6528 34.4491 53.014 34.3307 53.4201C34.1959 54.0429 34.1959 54.6873 34.3307 55.3101C34.326 55.3433 34.326 55.377 34.3307 55.4102C34.387 55.8446 34.5598 56.2558 34.8307 56.6002C34.854 56.6391 34.8807 56.6759 34.9106 56.7101C35.0264 56.8218 35.1161 56.9577 35.1732 57.1082C35.2302 57.2586 35.2533 57.4198 35.2407 57.5801C35.0726 57.8569 34.8505 58.0969 34.5876 58.2859C34.3247 58.4749 34.0265 58.6089 33.7107 58.6801C33.3644 58.7412 33.028 58.8489 32.7107 59.0001L32.6107 59.0601C32.5433 59.1011 32.4797 59.1479 32.4206 59.2001L32.3407 59.2802C32.2859 59.3395 32.2357 59.4031 32.1906 59.4702L32.1306 59.5801L32.0706 59.7101C32.0667 59.75 32.0667 59.7902 32.0706 59.8301C32.065 59.8733 32.065 59.917 32.0706 59.9601C32.0741 59.9867 32.0741 60.0135 32.0706 60.0401C32.6082 60.766 33.3084 61.3558 34.1151 61.7622C34.9217 62.1686 35.8124 62.3803 36.7157 62.3803C37.6189 62.3803 38.5096 62.1686 39.3163 61.7622C40.1229 61.3558 40.8231 60.766 41.3607 60.0401C41.3607 60.0401 41.3607 59.9801 41.3607 59.9601H40.9306Z" fill="white"/>
<path d="M122.86 83.0704C122.534 82.7264 122.14 82.4544 121.702 82.2719C121.265 82.0893 120.794 82.0003 120.32 82.0104C119.858 82.001 119.398 82.0846 118.969 82.2564C118.539 82.4282 118.149 82.6846 117.82 83.0104C117.483 83.3324 117.215 83.7206 117.034 84.1508C116.854 84.581 116.764 85.0438 116.77 85.5104V86.5104H115.77V82.5104C115.774 81.6779 115.492 80.8694 114.97 80.2204C114.483 79.5632 113.771 79.1077 112.97 78.9404C112.729 78.8998 112.485 78.8797 112.24 78.8804C111.3 78.8804 110.399 79.253 109.733 79.9166C109.068 80.5802 108.693 81.4806 108.69 82.4203V86.4203H107.69V82.5604C107.712 81.5268 107.357 80.5207 106.69 79.7303C106.368 79.3381 105.969 79.0147 105.519 78.7794C105.069 78.544 104.576 78.4015 104.07 78.3604C103.934 78.3477 103.797 78.3477 103.66 78.3604C103.126 78.3526 102.596 78.4542 102.102 78.6589C101.609 78.8635 101.162 79.1669 100.79 79.5504C100.407 79.9199 100.104 80.3648 99.9011 80.857C99.698 81.3492 99.599 81.8781 99.6102 82.4104V86.4104H98.6102V73.6404V59.4404C98.6211 58.6568 98.3751 57.8912 97.9101 57.2604C97.8107 57.1158 97.6969 56.9817 97.5702 56.8604C96.967 56.2522 96.156 55.8949 95.3001 55.8604H94.8202C93.9643 55.8949 93.1533 56.2522 92.5501 56.8604C92.4262 56.9843 92.3126 57.1181 92.2102 57.2604C91.7452 57.8912 91.4992 58.6568 91.5101 59.4404V83.4404C91.5139 83.497 91.5139 83.5537 91.5101 83.6104V97.1804L90.5101 96.2303V90.7903C90.5224 90.3155 90.4403 89.843 90.2686 89.4001C90.097 88.9573 89.8392 88.5529 89.5101 88.2104C89.1829 87.8679 88.7883 87.5969 88.3512 87.4144C87.914 87.232 87.4438 87.1421 86.9702 87.1504C86.5045 87.1498 86.0433 87.2423 85.614 87.4227C85.1846 87.603 84.7958 87.8674 84.4702 88.2004C84.1335 88.5231 83.8666 88.9115 83.686 89.3415C83.5054 89.7715 83.4149 90.234 83.4201 90.7004V97.7903C83.4174 98.2751 83.5146 98.7553 83.7055 99.2009C83.8965 99.6465 84.1772 100.048 84.5301 100.38L94.3801 109.74C94.795 110.164 95.119 110.669 95.3322 111.222C95.5455 111.776 95.6434 112.368 95.6201 112.96C95.6212 113.225 95.6748 113.487 95.7778 113.731C95.8808 113.974 96.0312 114.195 96.2202 114.38C96.4037 114.574 96.6257 114.728 96.8719 114.831C97.1181 114.934 97.3831 114.985 97.6501 114.98H117.95C118.481 114.98 118.989 114.77 119.364 114.395C119.739 114.02 119.95 113.511 119.95 112.98V112.07C119.943 111.247 120.047 110.426 120.26 109.63L123.67 95.8404C123.886 95.0452 123.994 94.2244 123.99 93.4004V85.6204C123.982 85.1414 123.877 84.6691 123.683 84.2311C123.489 83.7932 123.209 83.3985 122.86 83.0704Z" fill="white"/>
<path d="M127.94 85.6397C127.955 84.6323 127.765 83.6323 127.382 82.7006C126.999 81.7688 126.43 80.9248 125.71 80.2197C124.773 79.2918 123.613 78.6205 122.342 78.2703C121.07 77.92 119.73 77.9026 118.45 78.2197C117.345 76.6425 115.685 75.5419 113.802 75.1393C111.919 74.7368 109.953 75.062 108.3 76.0497C106.946 75.0067 105.279 74.4502 103.57 74.4697C103.236 74.4696 102.902 74.4896 102.57 74.5297V59.4397C102.586 58.4331 102.397 57.4337 102.016 56.5021C101.634 55.5704 101.068 54.7259 100.35 54.0197C99.3935 53.0386 98.1809 52.3457 96.85 52.0197H96.7C96.5251 51.98 96.3482 51.9499 96.17 51.9297H95.85C95.59 51.9297 95.32 51.9297 95.04 51.9297H94.9C94.63 51.9297 94.36 51.9297 94.09 51.9297H93.78C93.6017 51.9491 93.4246 51.9792 93.2499 52.0197H93.09C91.7613 52.3439 90.5514 53.0372 89.6 54.0197C88.8812 54.7254 88.3129 55.5696 87.9296 56.5012C87.5463 57.4328 87.3559 58.4324 87.3699 59.4397V83.0697C87.2069 83.0556 87.043 83.0556 86.8799 83.0697C84.8667 83.075 82.9372 83.8757 81.5118 85.2974C80.0863 86.7191 79.2805 88.6465 79.2699 90.6597V97.7497C79.2589 98.7902 79.4689 99.8213 79.886 100.775C80.3032 101.728 80.9181 102.582 81.69 103.28L91.47 112.64C91.4322 114.308 92.0455 115.926 93.18 117.15C93.736 117.752 94.4147 118.228 95.1703 118.545C95.9259 118.862 96.7408 119.014 97.56 118.99H117.86C118.662 119.002 119.459 118.853 120.202 118.552C120.946 118.251 121.622 117.803 122.19 117.237C122.757 116.67 123.206 115.995 123.508 115.251C123.811 114.508 123.961 113.712 123.95 112.91C123.926 112.155 123.99 111.4 124.14 110.66L127.58 96.8697C127.842 95.7483 127.98 94.6013 127.99 93.4497L127.94 85.6397ZM123.57 95.8697L120.18 109.66C119.967 110.455 119.862 111.276 119.87 112.1V112.89C119.87 113.42 119.659 113.929 119.284 114.304C118.909 114.679 118.4 114.89 117.87 114.89H97.57C97.303 114.895 97.0379 114.844 96.7917 114.74C96.5454 114.637 96.3235 114.484 96.1399 114.29C95.951 114.104 95.8007 113.884 95.6977 113.64C95.5947 113.396 95.5411 113.134 95.54 112.87C95.5632 112.277 95.4653 111.686 95.2521 111.132C95.0389 110.578 94.7149 110.074 94.3 109.65L84.45 100.29C84.097 99.9573 83.8163 99.5559 83.6253 99.1103C83.4343 98.6647 83.3373 98.1845 83.34 97.6997V90.6097C83.3348 90.1433 83.4252 89.6809 83.6058 89.2509C83.7864 88.8208 84.0533 88.4324 84.3899 88.1097C84.7155 87.7767 85.1045 87.5123 85.5339 87.332C85.9632 87.1516 86.4243 87.0591 86.8899 87.0597C87.3636 87.0514 87.8338 87.1414 88.2709 87.3238C88.7081 87.5062 89.1027 87.7772 89.43 88.1197C89.7591 88.4622 90.0169 88.8666 90.1885 89.3095C90.3602 89.7524 90.4423 90.2249 90.43 90.6997V96.1397L91.43 97.0897V83.5597C91.4337 83.5031 91.4337 83.4463 91.43 83.3897V59.3897C91.4191 58.6061 91.6649 57.8405 92.1299 57.2097C92.2323 57.0675 92.3461 56.9337 92.47 56.8097C93.0732 56.2016 93.8842 55.8443 94.74 55.8097H95.22C96.0758 55.8443 96.8869 56.2016 97.49 56.8097C97.6167 56.9311 97.7306 57.0651 97.83 57.2097C98.295 57.8405 98.5408 58.6061 98.53 59.3897V73.5897V86.5397H99.53V82.5397C99.5188 82.0074 99.6178 81.4785 99.8209 80.9863C100.024 80.4941 100.327 80.0492 100.71 79.6797C101.082 79.2962 101.528 78.9928 102.022 78.7882C102.516 78.5836 103.046 78.482 103.58 78.4897C103.716 78.477 103.854 78.477 103.99 78.4897C104.496 78.5308 104.989 78.6733 105.439 78.9087C105.889 79.144 106.287 79.4674 106.61 79.8597C107.276 80.65 107.632 81.6561 107.61 82.6897V86.5497H108.61V82.5497C108.613 81.6099 108.988 80.7096 109.653 80.046C110.319 79.3824 111.22 79.0097 112.16 79.0097C112.405 79.0091 112.649 79.0291 112.89 79.0697C113.691 79.237 114.403 79.6925 114.89 80.3497C115.411 80.9987 115.694 81.8072 115.69 82.6397V86.6397H116.69V85.6397C116.684 85.1732 116.773 84.7103 116.954 84.2802C117.135 83.85 117.402 83.4617 117.74 83.1397C118.068 82.8139 118.459 82.5575 118.888 82.3857C119.318 82.214 119.778 82.1303 120.24 82.1397C120.714 82.1296 121.185 82.2186 121.622 82.4012C122.059 82.5837 122.454 82.8557 122.78 83.1997C123.109 83.5422 123.367 83.9466 123.539 84.3895C123.71 84.8323 123.792 85.3049 123.78 85.7797V93.5597C123.808 94.3355 123.738 95.1117 123.57 95.8697Z" fill="#0056B2"/>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

+210
View File
@@ -0,0 +1,210 @@
<?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/>.
/**
* Renderer for the grade user report
*
* @package gradereport_user
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\output\comboboxsearch;
/**
* Custom renderer for the user grade report
*
* To get an instance of this use the following code:
* $renderer = $PAGE->get_renderer('gradereport_user');
*
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradereport_user_renderer extends plugin_renderer_base {
/**
* Small rendering function that helps with outputting the relevant user selector.
*
* @param string $report
* @param stdClass $course
* @param int $userid
* @param null|int $groupid
* @param bool $includeall
* @return string The raw HTML to render.
* @throws coding_exception
*/
public function graded_users_selector(string $report, stdClass $course, int $userid, ?int $groupid, bool $includeall): string {
$select = grade_get_graded_users_select($report, $course, $userid, $groupid, $includeall);
$output = html_writer::tag('div', $this->output->render($select), ['id' => 'graded_users_selector']);
$output .= html_writer::tag('p', '', ['style' => 'page-break-after: always;']);
return $output;
}
/**
* Creates and renders the single select box for the user view.
*
* @param int $userid The selected userid
* @param int $userview The current view user setting constant
* @return string
*/
public function view_user_selector(int $userid, int $userview): string {
global $USER;
$url = $this->page->url;
if ($userid != $USER->id) {
$url->param('userid', $userid);
}
$options = [
GRADE_REPORT_USER_VIEW_USER => get_string('otheruser', 'grades'),
GRADE_REPORT_USER_VIEW_SELF => get_string('myself', 'grades')
];
$select = new single_select($url, 'userview', $options, $userview, null);
$select->label = get_string('viewas', 'grades');
$output = html_writer::tag('div', $this->output->render($select), ['class' => 'view_users_selector']);
return $output;
}
/**
* Renders the user selector trigger element.
*
* @param object $course The course object.
* @param int|null $userid The user ID.
* @param int|null $groupid The group ID.
* @return string The raw HTML to render.
* @throws coding_exception
*/
public function users_selector(object $course, ?int $userid = null, ?int $groupid = null): string {
$resetlink = new moodle_url('/grade/report/user/index.php', ['id' => $course->id, 'group' => 0]);
$submitteduserid = optional_param('userid', '', PARAM_INT);
if ($submitteduserid) {
$user = core_user::get_user($submitteduserid);
$currentvalue = fullname($user);
} else {
$currentvalue = '';
}
$data = [
'currentvalue' => $currentvalue,
'instance' => rand(),
'resetlink' => $resetlink->out(false),
'name' => 'userid',
'value' => $submitteduserid ?? '',
'courseid' => $course->id,
'group' => $groupid ?? 0,
];
$searchdropdown = new comboboxsearch(
true,
$this->render_from_template('core_user/comboboxsearch/user_selector', $data),
null,
'user-search d-flex',
null,
'usersearchdropdown overflow-auto',
null,
false,
);
$this->page->requires->js_call_amd('gradereport_user/user', 'init');
return $this->render_from_template($searchdropdown->get_template(), $searchdropdown->export_for_template($this));
}
/**
* Creates and renders previous/next user navigation.
*
* @param graded_users_iterator $gui Objects that is used to iterate over a list of gradable users in the course.
* @param int $userid The ID of the current user.
* @param int $courseid The course ID.
* @return string The raw HTML to render.
*/
public function user_navigation(graded_users_iterator $gui, int $userid, int $courseid): string {
$navigationdata = [];
$users = [];
while ($userdata = $gui->next_user()) {
$users[$userdata->user->id] = $userdata->user;
}
$arraykeys = array_keys($users);
$keynumber = array_search($userid, $arraykeys);
// Without a valid user or users list, there's nothing to render.
if ($keynumber === false) {
return '';
}
// Determine directionality so that icons can be modified to suit language.
$previousarrow = right_to_left() ? 'right' : 'left';
$nextarrow = right_to_left() ? 'left' : 'right';
// If the current user is not the first one in the list, find and render the previous user.
if ($keynumber !== 0) {
$previoususer = $users[$arraykeys[$keynumber - 1]];
$navigationdata['previoususer'] = [
'name' => fullname($previoususer),
'url' => (new moodle_url('/grade/report/user/index.php', ['id' => $courseid, 'userid' => $previoususer->id]))
->out(false),
'previousarrow' => $previousarrow
];
}
// If the current user is not the last one in the list, find and render the last user.
if ($keynumber < count($users) - 1) {
$nextuser = $users[$arraykeys[$keynumber + 1]];
$navigationdata['nextuser'] = [
'name' => fullname($nextuser),
'url' => (new moodle_url('/grade/report/user/index.php', ['id' => $courseid, 'userid' => $nextuser->id]))
->out(false),
'nextarrow' => $nextarrow
];
}
return $this->render_from_template('gradereport_user/user_navigation', $navigationdata);
}
/**
* Creates and renders 'view report as' selector element.
*
* @param int $userid The selected userid
* @param int $userview The current view user setting constant
* @param int $courseid The course ID.
* @return string The raw HTML to render.
*/
public function view_mode_selector(int $userid, int $userview, int $courseid): string {
$viewasotheruser = new moodle_url('/grade/report/user/index.php', ['id' => $courseid, 'userid' => $userid,
'userview' => GRADE_REPORT_USER_VIEW_USER]);
$viewasmyself = new moodle_url('/grade/report/user/index.php', ['id' => $courseid, 'userid' => $userid,
'userview' => GRADE_REPORT_USER_VIEW_SELF]);
$selectoroptions = [
$viewasotheruser->out(false) => get_string('otheruser', 'core_grades'),
$viewasmyself->out(false) => get_string('myself', 'core_grades')
];
$selectoractiveurl = $userview === GRADE_REPORT_USER_VIEW_USER ? $viewasotheruser : $viewasmyself;
$viewasselect = new \core\output\select_menu('viewas', $selectoroptions, $selectoractiveurl->out(false));
$viewasselect->set_label(get_string('viewas', 'core_grades'));
return $this->render_from_template('gradereport_user/view_mode_selector',
$viewasselect->export_for_template($this));
}
}
+154
View File
@@ -0,0 +1,154 @@
<?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/>.
/**
* Defines site settings for the user gradebook report
*
* @package gradereport_user
* @copyright 2007 Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
$settings->add(
new admin_setting_configcheckbox(
'grade_report_user_showrank',
get_string('showrank', 'grades'),
get_string('showrank_help', 'grades'),
0
)
);
$settings->add(
new admin_setting_configcheckbox(
'grade_report_user_showpercentage',
get_string('showpercentage', 'grades'),
get_string('showpercentage_help', 'grades'),
1
)
);
$settings->add(
new admin_setting_configcheckbox(
'grade_report_user_showgrade',
get_string('showgrade', 'grades'),
get_string('showgrade_help', 'grades'),
1
)
);
$settings->add(
new admin_setting_configcheckbox(
'grade_report_user_showfeedback',
get_string('showfeedback', 'grades'),
get_string('showfeedback_help', 'grades'),
1
)
);
$settings->add(
new admin_setting_configcheckbox(
'grade_report_user_showrange',
get_string('showrange', 'grades'),
get_string('showrange_help', 'grades'),
1
)
);
$settings->add(
new admin_setting_configcheckbox(
'grade_report_user_showweight',
get_string('showweight', 'grades'),
get_string('showweight_help', 'grades'),
1
)
);
$settings->add(
new admin_setting_configcheckbox(
'grade_report_user_showaverage',
get_string('showaverage', 'grades'),
get_string('showaverage_help', 'grades'),
0
)
);
$settings->add(
new admin_setting_configcheckbox(
'grade_report_user_showlettergrade',
get_string('showlettergrade', 'grades'),
get_string('showlettergrade_help', 'grades'),
0
)
);
$settings->add(
new admin_setting_configselect(
'grade_report_user_rangedecimals',
get_string('rangedecimals', 'grades'),
get_string('rangedecimals_help', 'grades'),
0,
[
0 => 0,
1 => 1,
2 => 2,
3 => 3,
4 => 4,
5 => 5
]
)
);
$options = [
0 => get_string('shownohidden', 'grades'),
1 => get_string('showhiddenuntilonly', 'grades'),
2 => get_string('showallhidden', 'grades')
];
$settings->add(
new admin_setting_configselect(
'grade_report_user_showhiddenitems',
get_string('showhiddenitems', 'grades'),
get_string('showhiddenitems_help', 'grades'),
1,
$options
)
);
$settings->add(
new admin_setting_configselect(
'grade_report_user_showtotalsifcontainhidden',
get_string('hidetotalifhiddenitems', 'grades'),
get_string('hidetotalifhiddenitems_help', 'grades'),
GRADE_REPORT_HIDE_TOTAL_IF_CONTAINS_HIDDEN,
[
GRADE_REPORT_HIDE_TOTAL_IF_CONTAINS_HIDDEN => get_string('hide'),
GRADE_REPORT_SHOW_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowexhiddenitems', 'grades'),
GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowinchiddenitems', 'grades')
]
)
);
$settings->add(
new admin_setting_configcheckbox(
'grade_report_user_showcontributiontocoursetotal', get_string('showcontributiontocoursetotal', 'grades'),
get_string('showcontributiontocoursetotal_help', 'grades'),
1
)
);
}
+190
View File
@@ -0,0 +1,190 @@
.path-grade-report-user #graded_users_selector {
float: right;
margin-bottom: 5px;
}
.path-grade-report-user .view_users_selector {
clear: both;
float: right;
margin-bottom: 5px;
}
.path-grade-report-user #graded_users_selector .singleselect label,
.path-grade-report-user .view_users_selector .singleselect label {
display: inline-block;
}
.path-grade-report-user .user-grade,
.grade-report-user .user-grade {
width: 100%;
}
.path-grade-report-user .user-grade thead th,
.grade-report-user .user-grade thead th {
vertical-align: bottom;
border: none;
text-align: left;
background-color: #f8f9fa;
}
.path-grade-report-user .user-grade td,
.grade-report-user .user-grade td {
min-width: 4.5em;
background-color: #f8f9fa;
border: none;
vertical-align: middle;
}
.path-grade-report-user .user-grade .b1l,
.grade-report-user .user-grade .b1l {
padding: 0;
width: 24px;
min-width: 24px;
}
.path-grade-report-user .user-grade tbody .column-itemname,
.grade-report-user .user-grade tbody .column-itemname {
padding-left: 24px;
padding-right: 8px;
}
.path-grade-report-user .user-grade .column-itemname.baggt,
.path-grade-report-user .user-grade .column-itemname.baggb,
.grade-report-user .user-grade .column-itemname.baggt,
.grade-report-user .user-grade .column-itemname.baggb {
padding-left: 0;
}
.path-grade-report-user .user-grade .baggt,
.path-grade-report-user .user-grade .baggb,
.grade-report-user .user-grade .baggt,
.grade-report-user .user-grade .baggb {
font-weight: bold;
background-color: #f8f9fa;
border: none;
}
.path-grade-report-user .user-report-container,
.grade-report-user .user-report-container {
margin: 20px 0 30px 0;
padding: 10px 10px;
background-color: #f8f9fa;
}
.path-grade-report-user .user-grade tbody tr,
.grade-report-user .user-grade tbody tr {
background-color: #f8f9fa;
}
.path-grade-report-user .user-grade tbody tr:hover,
.grade-report-user .user-grade tbody tr:hover {
color: inherit;
background-color: #f8f9fa;
}
.path-grade-report-user .user-grade tbody tr.spacer,
.grade-report-user .user-grade tbody tr.spacer {
height: 0.5rem;
}
.path-grade-report-user .user-grade td.item,
.grade-report-user .user-grade td.item {
background-color: white;
border-top: 1px solid #dee2e6;
border-bottom: 1px solid #dee2e6;
}
.path-grade-report-user .user-grade th.column-itemname:not(.header,.category,.baggt,.baggb),
.grade-report-user .user-grade th.column-itemname:not(.header,.category,.baggt,.baggb) {
background-color: white;
font-weight: normal;
border-bottom: 1px solid #dee2e6;
}
.path-grade-report-user .user-grade th.category,
.grade-report-user .user-grade th.category {
background-color: white;
border: 1px solid #dee2e6;
padding-left: 10px;
font-weight: bold;
}
.path-grade-report-user .user-grade th.category a[aria-expanded="true"] .expanded,
.path-grade-report-user .user-grade th.category a[aria-expanded="false"] .collapsed,
.grade-report-user .user-grade th.category a[aria-expanded="true"] .expanded,
.grade-report-user .user-grade th.category a[aria-expanded="false"] .collapsed {
display: none;
}
.path-grade-report-user .user-grade th.category a.toggle-category,
.grade-report-user .user-grade th.category a.toggle-category {
height: 24px;
width: 24px;
font-size: 12px;
line-height: 24px;
margin-right: 3px;
}
.path-grade-report-user .user-grade th.category a.toggle-category i,
.grade-report-user .user-grade th.category a.toggle-category i {
font-size: 12px;
width: 12px;
height: 12px;
color: #1d2125;
margin: 0;
}
.path-grade-report-user .user-grade .column-itemname .small,
.grade-report-user .user-grade .column-itemname .small {
font-size: 70%;
}
.path-grade-report-user .user-grade tr[data-hidden="true"],
.grade-report-user .user-grade tr[data-hidden="true"] {
display: none;
}
.path-grade-report-user .user-grade .category div,
.grade-report-user .user-grade .category div {
min-height: 30px;
align-items: center;
}
.path-grade-report-user .search-widget[data-searchtype="user"] .userinitials {
width: 40px;
height: 40px;
}
.path-grade-report-user .user-navigation .container {
max-width: 767px;
}
.path-grade-report-user .view-user-selector label {
display: block;
font-size: 80%;
margin: 0;
}
.path-grade-report-user .view-user-selector .dropdown-toggle {
padding: 0;
font-weight: bold;
}
.path-grade-report-user .zero-state img {
width: 185px;
}
@media print {
.path-grade-report-user .user-grade th.category,
.grade-report-user .user-grade th.category {
border-left: none;
border-right: none;
}
.path-grade-report-user .user-grade th.category a.toggle-category,
.grade-report-user .user-grade th.category a.toggle-category
.path-grade-report-user #page-footer,
.path-grade-report-user .user-grade td.column-grade .action-menu {
display: none;
}
}
@@ -0,0 +1,106 @@
{{!
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 gradereport_user/action_bar
Actions bar for the grade user report.
Context variables required for this template:
* generalnavselector - The data object containing the required properties to render the general navigation selector.
* groupselector - (optional) HTML that outputs the group selector
* userselector - (optional) The data object containing the required properties to render the user selector
* viewasselector - (optional) HTML that outputs the 'view report as' selector
Example context (json):
{
"generalnavselector": {
"name": "Gradebook tertiary navigation selector",
"value": "opt2",
"baseid": "select-menu56789",
"selectedoption": "Gradebook setup",
"options": [
{
"selected": false,
"isgroup": {
"name": "View",
"id": "select-menu-group1",
"options": [
{
"name": "Grader report",
"value": "opt1",
"id": "select-menu-option1",
"selected": false
}
]
}
},
{
"selected": false,
"isgroup": {
"name": "Setup",
"id": "select-menu-group2",
"options": [
{
"name": "Gradebook setup",
"value": "opt2",
"id": "select-menu-option2",
"selected": true
}
]
}
}
]
},
"groupselector": "<div class='group-selector'></div>",
"userselector": {
"content": "<div class='user-selector'></div>",
"courseid": 25
},
"viewasselector": "<div class='view-user-selector'></div>"
}
}}
<div class="container-fluid tertiary-navigation full-width-bottom-border">
<div class="row">
{{#generalnavselector}}
<div class="navitem">
{{>core/tertiary_navigation_selector}}
</div>
<div class="navitem-divider"></div>
{{/generalnavselector}}
{{#groupselector}}
<div class="navitem">
{{{groupselector}}}
</div>
<div class="navitem-divider"></div>
{{/groupselector}}
{{#userselector}}
<span class="d-none" data-region="courseid" data-courseid="{{courseid}}" aria-hidden="true"></span>
<div class="navitem flex-column">
{{#content}}
{{{.}}}
{{/content}}
</div>
<div class="navitem-divider"></div>
{{/userselector}}
<div class="d-flex ml-sm-auto">
{{#viewasselector}}
<div class="navitem-divider"></div>
<div class="navitem">
{{{viewasselector}}}
</div>
<div class="navitem-divider"></div>
{{/viewasselector}}
</div>
</div>
</div>
@@ -0,0 +1,33 @@
{{!
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 gradereport_user/all_users_item
Search result line items.
Example context (json):
{
"id": "0",
"name": "All users(10)",
"url": "http://foo.bar/gradereport/?userid=0&id=2"
}
}}
<a id="item-{{id}}" href="{{url}}" class="dropdown-item d-flex px-2 py-1">
<span class="pull-left result-cell text-truncate mr-2">
{{name}}
</span>
</a>
@@ -0,0 +1,61 @@
{{!
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 gradereport_user/user_navigation
The previous/next user navigation for the user report view.
Context variables required for this template:
* previoususer - (optional) The object containing information about the previous user.
* name - The name of the previous user.
* url - The URL to the previous user report.
* nextuser - (optional) The object containing information about the next user.
* name - The name of the next user.
* url - The URL to the next user report.
Example context (json):
{
"previoususer": {
"name": "John Smith",
"url": "https://example.com/grade/report/user/index.php?id=2&userid=3"
},
"previoususer": {
"name": "Jane Doe",
"url": "https://example.com/grade/report/user/index.php?id=2&userid=5"
}
}
}}
<div class="user-navigation w-100">
<div class="container w-100 d-flex">
{{#previoususer}}
<div class="previous d-flex">
<a href="{{url}}" aria-label="{{#str}} gotopreviousreport, gradereport_user {{/str}}">
<i class="fa fa-caret-{{previousarrow}} fa-lg pr-1"></i>
{{name}}
</a>
</div>
{{/previoususer}}
{{#nextuser}}
<div class="next d-flex ml-auto">
<a href="{{url}}" aria-label="{{#str}} gotonextreport, gradereport_user {{/str}}">
{{name}}
<i class="fa fa-caret-{{nextarrow}} fa-lg pl-1"></i>
</a>
</div>
{{/nextuser}}
</div>
</div>
@@ -0,0 +1,39 @@
{{!
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 gradereport_user/user_report_category_content
Template for the content of a category cell in the user report table.
Context variables required for this template:
* categoryid - The grade category ID
* categoryname - The grade category name
Example context (json):
{
"categoryid": "2",
"categoryname": "Grade category 1"
}
}}
<div class="d-flex category-content">
<a aria-expanded="true" role="button" data-categoryid={{categoryid}} data-target=".cat_{{categoryid}}[data-hidden='false']" class="btn btn-icon mr-1 toggle-category" href="#">
<span class="collapsed text-nowrap" title="{{#str}} collapse, core {{/str}}">
<i class="icon fa fa-chevron-down fa-fw" title="{{#str}} collapse, core {{/str}}" role="img" aria-label="{{#str}} collapse, core {{/str}}"></i>
</span>
<span class="expanded text-nowrap" title="{{#str}} expand, core {{/str}}">
<i class="icon fa fa-chevron-{{arrow}} fa-fw" title="{{#str}} expand, core {{/str}}" role="img" aria-label="{{#str}} expand, core {{/str}}"></i>
</span>
</a>
<span>{{{categoryname}}}</span>
</div>
@@ -0,0 +1,62 @@
{{!
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 gradereport_user/view_mode_selector
The view mode selector.
Context variables required for this template:
* name - name of the form element
* value - value of the form element
* baseid - id of the dropdown element and to be used to generate id for other elements used internally
* label - Element label
* labelattributes - Label attributes.
* selectedoption - Text of the selected option
* options - Array of options for the select with value, name, selected, isgroup and id properites.
Example context (json):
{
"name": "menuname",
"value": "opt2",
"baseid": "select-menu56789",
"label": "View report as",
"labelattributes": [],
"selectedoption": "Myself",
"options": [
{
"name": "User",
"value": "opt1",
"id": "select-menu-option1",
"selected": false
},
{
"name": "Myself",
"value": "opt2",
"id": "select-menu-option2",
"selected": true
}
]
}
}}
<div class="view-user-selector">
{{>core/select_menu}}
</div>
{{#js}}
document.querySelector('#{{baseid}}').addEventListener('change', function(e) {
window.location.href = e.target.value;
});
{{/js}}
@@ -0,0 +1,31 @@
{{!
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 gradereport_user/zero_state
The zero state of the user report that contains the image and trigger for the search widget.
Example context (json):
{
"imglink": "http://foo.bar/gradereport/?userid=25"
}
}}
<div class="zero-state mx-auto w-50 text-center my-6">
<img src="{{imglink}}" alt="{{#str}}userreports, gradereport_user{{/str}}" aria-hidden="true" class="my-5">
<h3>{{#str}}userreports, gradereport_user{{/str}}</h3>
<p>{{#str}}userreportdesc, gradereport_user{{/str}}</p>
</div>
@@ -0,0 +1,113 @@
@core @core_grades @gradereport_user @javascript
Feature: Group searching functionality within the user report.
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 |
| student1 | Student | 1 | student1@example.com | s1 |
| student2 | Student | 2 | student2@example.com | s2 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "groups" exist:
| name | course | idnumber |
| Default <span class="multilang" lang="de">Gruppe</span><span class="multilang" lang="en">group</span> | C1 | dg |
| Tutor <span class="multilang" lang="de">Gruppe</span><span class="multilang" lang="en">group</span> | C1 | tg |
| Marker <span class="multilang" lang="de">Gruppe</span><span class="multilang" lang="en">group</span> | C1 | mg |
And the following "group members" exist:
| user | group |
| student1 | dg |
And the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And I am on the "Course 1" "grades > User report > View" page logged in as "teacher1"
Scenario: A teacher can see the 'group' search widget only when group mode is enabled in the course
Given ".groupsearchwidget" "css_element" should exist
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| id_groupmode | No groups |
And I press "Save and display"
When I navigate to "View > User report" in the course gradebook
Then ".groupsearchwidget" "css_element" should not exist
Scenario: A teacher can search for and find a group to find a user in
Given I confirm "Tutor group" in "group" search within the gradebook widget exists
And I confirm "Marker group" in "group" search within the gradebook widget exists
When I set the field "Search groups" to "tutor"
And I wait "1" seconds
Then I confirm "Tutor group" in "group" search within the gradebook widget exists
And I confirm "Marker group" in "group" search within the gradebook widget does not exist
And I click on "Tutor group" in the "group" search widget
# The search input remains in the field on reload this is in keeping with other search implementations.
And I click on ".groupsearchwidget" "css_element"
And the field "Search groups" matches value "tutor"
Then I set the field "Search groups" to "Turtle"
And I should see "No results for \"Turtle\""
Scenario: A teacher can only see the group members in the 'user' search widget after selecting a group option
# Confirm that all users are initially displayed in the 'user' search widget.
Given I set the field "Search users" to "Student"
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Student 2" in "user" search within the gradebook widget exists
# Select a particular group from the 'group' search widget.
When I click on "Default group" in the "group" search widget
# Confirm that only users which are members of the selected group are displayed in the 'user' search widget.
And I set the field "Search users" to "Student"
Then I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Student 2" in "user" search within the gradebook widget does not exist
And I click on "Tutor group" in the "group" search widget
And I set the field "Search users" to "Student"
And I confirm "Student 1" in "user" search within the gradebook widget does not exist
And I confirm "Student 2" in "user" search within the gradebook widget does not exist
And I click on "All participants" in the "group" search widget
And I set the field "Search users" to "Student"
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Student 2" in "user" search within the gradebook widget exists
@accessibility
Scenario: A teacher can set focus and search using the input with a keyboard
# Basic tests for the page.
Given I click on ".groupsearchwidget" "css_element"
And the page should meet accessibility standards
And the page should meet "wcag131, wcag141, wcag412" accessibility standards
And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests
# Move onto general keyboard navigation testing.
And I click on "Search groups" "field"
And I wait until "Default group" "option_role" exists
And I press the down key
And the focused element is "Search groups" "field"
And ".active" "css_element" should exist in the "All participants" "option_role"
And I press the up key
And the focused element is "Search groups" "field"
And ".active" "css_element" should exist in the "Tutor group" "option_role"
And I press the down key
And the focused element is "Search groups" "field"
And ".active" "css_element" should exist in the "All participants" "option_role"
Then I set the field "Search groups" to "Goodmeme"
And I wait until "Tutor group" "option_role" does not exist
And I press the down key
And the focused element is "Search groups" "field"
And I navigate to "View > User report" in the course gradebook
And I click on ".groupsearchwidget" "css_element"
And I set the field "Search groups" to "Tutor"
And I wait until "All participants" "option_role" does not exist
And I press the down key
And the focused element is "Search groups" "field"
And ".active" "css_element" should exist in the "Tutor group" "option_role"
# Lets check the tabbing order.
And I set the field "Search groups" to "Marker"
And I wait until "Marker group" "option_role" exists
And I press the tab key
And the focused element is "Clear search input" "button"
And I press the enter key
And I wait until the page is ready
And ".groupsearchwidget" "css_element" should exist
@@ -0,0 +1,53 @@
@core @core_grades @gradereport_user @javascript
Feature: User can toggle the visibility of the grade categories within the user grade report.
In order to focus only on the information that I am interested in
As a teacher
I need to be able to easily toggle the visibility of grade categories in the user grade report
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 |
| student1 | Student | 1 | student1@example.com | s1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "grade categories" exist:
| fullname | course |
| Category 1 | C1 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | grade |
| assign | C1 | a1 | Test assignment one | Submit something! | 300 |
And the following "activities" exist:
| activity | course | idnumber | name | gradecategory | grade | gradepass |
| assign | C1 | a2 | Test assignment two | Category 1 | 100 | 50 |
Scenario: A teacher can search for and find a user to view
Given I am on the "Course" "grades > User report > View" page logged in as "teacher1"
And I click on "Student 1" in the "user" search widget
And I should see "Test assignment one" in the "user-grade" "table"
And I should see "Test assignment two" in the "user-grade" "table"
And I should see "Category 1 total" in the "user-grade" "table"
And I should see "Course total" in the "user-grade" "table"
# Hide the grade category 'Category 1'.
When I click on ".toggle-category" "css_element" in the "Category 1" "table_row"
Then I should not see "Test assignment two" in the "user-grade" "table"
And I should not see "Category 1 total" in the "user-grade" "table"
And I should see "Test assignment one" in the "user-grade" "table"
And I should see "Course total" in the "user-grade" "table"
# Show the grade category 'Category 1'.
And I click on ".toggle-category" "css_element" in the "Category 1" "table_row"
And I should see "Test assignment two" in the "user-grade" "table"
And I should see "Category 1 total" in the "user-grade" "table"
And I should see "Test assignment one" in the "user-grade" "table"
And I should see "Course total" in the "user-grade" "table"
# Hide the grade category 'Course'.
And I click on ".toggle-category" "css_element" in the "Course" "table_row"
And I should not see "Test assignment two" in the "user-grade" "table"
And I should not see "Category 1 total" in the "user-grade" "table"
And I should not see "Test assignment one" in the "user-grade" "table"
And I should not see "Course total" in the "user-grade" "table"
@@ -0,0 +1,62 @@
@core @core_grades @gradereport_user @javascript
Feature: Teacher can navigate to the previous or next user report.
In order to get go the previous or next user report
As a teacher
I need to click on the previous/next navigation links
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 |
| student1 | Student | 1 | student1@example.com | s1 |
| student2 | Student | 2 | student2@example.com | s2 |
| student3 | Student | 3 | student3@example.com | s3 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
And the following "activities" exist:
| activity | course | idnumber | name | grade |
| assign | C1 | a1 | Test assignment one | 300 |
And I am on the "Course" "grades > User report > View" page logged in as "teacher1"
Scenario: A teacher can navigate to the next user report
Given I click on "Student 1" in the "user" search widget
And "Student 1" "heading" should exist
And ".previous" "css_element" should not exist in the ".user-navigation" "css_element"
And ".next" "css_element" should exist in the ".user-navigation" "css_element"
And I should see "Student 2" in the ".next" "css_element"
When I click on "Student 2" "link" in the ".next" "css_element"
And "Student 2" "heading" should exist
And ".previous" "css_element" should exist in the ".user-navigation" "css_element"
And I should see "Student 1" in the ".previous" "css_element"
And ".next" "css_element" should exist in the ".user-navigation" "css_element"
And I should see "Student 3" in the ".next" "css_element"
And I click on "Student 3" "link" in the ".next" "css_element"
And "Student 3" "heading" should exist
And ".previous" "css_element" should exist in the ".user-navigation" "css_element"
And I should see "Student 2" in the ".previous" "css_element"
And ".next" "css_element" should not exist in the ".user-navigation" "css_element"
Scenario: A teacher can navigate to the previous user report
Given I click on "Student 3" in the "user" search widget
And "Student 3" "heading" should exist
And ".previous" "css_element" should exist in the ".user-navigation" "css_element"
And I should see "Student 2" in the ".previous" "css_element"
And ".next" "css_element" should not exist in the ".user-navigation" "css_element"
When I click on "Student 2" "link" in the ".previous" "css_element"
And "Student 2" "heading" should exist
And ".previous" "css_element" should exist in the ".user-navigation" "css_element"
And I should see "Student 1" in the ".previous" "css_element"
And ".next" "css_element" should exist in the ".user-navigation" "css_element"
And I should see "Student 3" in the ".next" "css_element"
And I click on "Student 1" "link" in the ".previous" "css_element"
And "Student 1" "heading" should exist
And ".previous" "css_element" should not exist in the ".user-navigation" "css_element"
And ".next" "css_element" should exist in the ".user-navigation" "css_element"
And I should see "Student 2" in the ".next" "css_element"
@@ -0,0 +1,234 @@
@core @core_grades @gradereport_user @javascript
Feature: View the user report as the student will see it
In order to know what grades students will see
As a teacher
I need to be able to view the user report as that other user
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| <span class="multilang" lang="en">Course</span><span class="multilang" lang="de">Kurs</span> 1 & '" | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 |
| student1 | Student | 1 | student1@example.com | s1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "grade categories" exist:
| fullname | course |
| Sub category 1 | C1 |
| Sub category 2 | C1 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | gradecategory | grade | gradepass |
| assign | C1 | a1 | Test assignment one | Submit something! | Sub category 1 | 100 | 50 |
| assign | C1 | a2 | Test assignment two | Submit something! | Sub category 1 | 100 | 50 |
| assign | C1 | a3 | Test assignment three | Submit something! | Sub category 2 | 100 | |
| assign | C1 | a4 | Test assignment four | Submit something! | Sub category 2 | 100 | |
And the following "activities" exist:
| activity | course | idnumber | name | intro | grade |
| assign | C1 | a5 | Test assignment five | Submit something! | 100 |
| assign | C1 | a6 | Test assignment six | Submit something! | 100 |
And the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
When I am on the "C1" "grades > gradebook setup" page logged in as "teacher1"
And I hide the grade item "Test assignment six" of type "gradeitem" on "setup" page
And I hide the grade item "Sub category 2" of type "category" on "setup" page
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I change window size to "large"
And I give the grade "80.00" to the user "Student 1" for the grade item "Test assignment one"
And I give the grade "35.00" to the user "Student 1" for the grade item "Test assignment two"
And I give the grade "100.00" to the user "Student 1" for the grade item "Test assignment three"
And I give the grade "50.00" to the user "Student 1" for the grade item "Test assignment four"
And I give the grade "21.00" to the user "Student 1" for the grade item "Test assignment five"
And I give the grade "97.00" to the user "Student 1" for the grade item "Test assignment six"
And I press "Save changes"
And I change window size to "medium"
Scenario: View the report as the teacher themselves
Given the following "activities" exist:
| activity | course | idnumber | name | intro | grade |
| quiz | C1 | q1 | Test quiz one | Submit something! | 100 |
When I navigate to "View > User report" in the course gradebook
And I click on "Student 1" in the "user" search widget
And I should see "Course 1 & '\""
And I should not see "Course 1 &amp; '\""
And I set the field "View report as" to "Myself"
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | 50.00 % | 80.00 | 0100 | 80.00 % | 13.33 % |
| Test assignment two | 50.00 % | 35.00 | 0100 | 35.00 % | 5.83 % |
| Sub category 1 total | 33.33 % | 115.00 | 0200 | 57.50 % | - |
| Test assignment three | 50.00 % | 100.00 | 0100 | 100.00 % | 16.67 % |
| Test assignment four | 50.00 % | 50.00 | 0100 | 50.00 % | 8.33 % |
| Sub category 2 total | 33.33 % | 150.00 | 0200 | 75.00 % | - |
| Test assignment five | 16.67 % | 21.00 | 0100 | 21.00 % | 3.50 % |
| Test assignment six | 16.67 % | 97.00 | 0100 | 97.00 % | 16.17 % |
| Test quiz one | 0.00 %( Empty ) | - | 0100 | - | 0.00 % |
| Course total | - | 383.00 | 0600 | 63.83 % | - |
# Confirm a contextual menu with a link to the Grade analysis page is available for "Test quiz one".
And I open the action menu in "Test quiz one" "table_row"
And "Grade analysis" "link" should exist
Scenario: View the report as the student from both the teachers and students perspective
Given the following "activities" exist:
| activity | course | idnumber | name | intro | grade |
| quiz | C1 | q1 | Test quiz one | Submit something! | 100 |
When I navigate to "View > User report" in the course gradebook
And I click on "Student 1" in the "user" search widget
And I set the field "View report as" to "User"
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | - | 80.00 | 0100 | 80.00 % | - |
| Test assignment two | - | 35.00 | 0100 | 35.00 % | - |
| Sub category 1 total | 33.33 % | - | 0200 | - | - |
| Test assignment five | - | 21.00 | 0100 | 21.00 % | - |
| Test quiz one | - | - | 0100 | - | - |
| Course total | - | - | 0600 | - | - |
And "//i[@aria-label='Pass']" "xpath_element" should exist in the "Test assignment one" "table_row"
And "//i[@aria-label='Fail']" "xpath_element" should exist in the "Test assignment two" "table_row"
And the following should not exist in the "user-grade" table:
| Grade item |
| Test assignment three |
| Test assignment four |
| Sub category 2 total |
| Test assignment six |
# Confirm a contextual menu with a link to the Grade analysis page is available for "Test quiz one".
And I open the action menu in "Test quiz one" "table_row"
And "Grade analysis" "link" should exist
And I log out
And I am on the "C1" "Course" page logged in as "student1"
And I navigate to "User report" in the course gradebook
And the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | - | 80.00 | 0100 | 80.00 % | - |
| Test assignment two | - | 35.00 | 0100 | 35.00 % | - |
| Sub category 1 total | 33.33 % | - | 0200 | - | - |
| Test assignment five | - | 21.00 | 0100 | 21.00 % | - |
| Test quiz one | - | - | 0100 | - | - |
| Course total | - | - | 0600 | - | - |
And "//i[@aria-label='Pass']" "xpath_element" should exist in the "Test assignment one" "table_row"
And "//i[@aria-label='Fail']" "xpath_element" should exist in the "Test assignment two" "table_row"
And the following should not exist in the "user-grade" table:
| Grade item |
| Test assignment three |
| Test assignment four |
| Sub category 2 total |
| Test assignment six |
# Confirm a contextual menu with a link to the Grade analysis page is available for "Test quiz one".
And I open the action menu in "Test quiz one" "table_row"
And "Grade analysis" "link" should exist
Scenario: View the report as the student from both the teachers and students perspective with totals excluding hidden
Given I navigate to "Setup > Course grade settings" in the course gradebook
And I set the field with xpath "//select[@name='report_user_showtotalsifcontainhidden']" to "Show totals excluding hidden items"
And I press "Save changes"
And I navigate to "View > User report" in the course gradebook
When I click on "Student 1" in the "user" search widget
And I set the field "View report as" to "User"
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | 50.00 % | 80.00 | 0100 | 80.00 % | 26.67 % |
| Test assignment two | 50.00 % | 35.00 | 0100 | 35.00 % | 11.67 % |
| Sub category 1 total | 66.67 % | 115.00 | 0200 | 57.50 | - |
| Test assignment five | 33.33 % | 21.00 | 0100 | 21.00 % | 7.00 % |
| Course total | - | 136.00 | 0300 | 45.33 % | - |
And the following should not exist in the "user-grade" table:
| Grade item |
| Test assignment three |
| Test assignment four |
| Sub category 2 total |
| Test assignment six |
And I log out
And I am on the "C1" "Course" page logged in as "student1"
And I navigate to "User report" in the course gradebook
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | 50.00 % | 80.00 | 0100 | 80.00 % | 26.67 % |
| Test assignment two | 50.00 % | 35.00 | 0100 | 35.00 % | 11.67 % |
| Sub category 1 total | 66.67 % | 115.00 | 0200 | 57.50 | - |
| Test assignment five | 33.33 % | 21.00 | 0100 | 21.00 % | 7.00 % |
| Course total | - | 136.00 | 0300 | 45.33 % | - |
And the following should not exist in the "user-grade" table:
| Grade item |
| Test assignment three |
| Test assignment four |
| Sub category 2 total |
| Test assignment six |
Scenario: View the report as the student from both the teachers and students perspective with totals including hidden
Given I navigate to "Setup > Course grade settings" in the course gradebook
And I set the field with xpath "//select[@name='report_user_showtotalsifcontainhidden']" to "Show totals including hidden items"
And I press "Save changes"
And I navigate to "View > User report" in the course gradebook
When I click on "Student 1" in the "user" search widget
And I set the field "View report as" to "User"
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | 50.00 % | 80.00 | 0100 | 80.00 % | 13.33 % |
| Test assignment two | 50.00 % | 35.00 | 0100 | 35.00 % | 5.83 % |
| Sub category 1 total | 33.33 % | 115.00 | 0200 | 57.50 % | - |
| Test assignment five | 16.67 % | 21.00 | 0100 | 21.00 % | 3.50 % |
| Course total | - | 383.00 | 0600 | 63.83 % | - |
And the following should not exist in the "user-grade" table:
| Grade item |
| Test assignment three |
| Test assignment four |
| Sub category 2 total |
| Test assignment six |
And I log out
And I am on the "C1" "Course" page logged in as "student1"
And I navigate to "User report" in the course gradebook
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | 50.00 % | 80.00 | 0100 | 80.00 % | 13.33 % |
| Test assignment two | 50.00 % | 35.00 | 0100 | 35.00 % | 5.83 % |
| Sub category 1 total | 33.33 % | 115.00 | 0200 | 57.50 % | - |
| Test assignment five | 16.67 % | 21.00 | 0100 | 21.00 % | 3.50 % |
| Course total | - | 383.00 | 0600 | 63.83 % | - |
And the following should not exist in the "user-grade" table:
| Grade item |
| Test assignment three |
| Test assignment four |
| Sub category 2 total |
| Test assignment six |
Scenario: View the report as the student from both the teachers and students perspective when the student can view hidden
Given the following "role capability" exists:
| role | student |
| moodle/grade:viewhidden | allow |
And I am on the "C1" "Course" page logged in as "teacher1"
And I navigate to "Setup > Course grade settings" in the course gradebook
And I set the field with xpath "//select[@name='report_user_showtotalsifcontainhidden']" to "Show totals excluding hidden items"
And I press "Save changes"
And I navigate to "View > User report" in the course gradebook
When I click on "Student 1" in the "user" search widget
And I set the field "View report as" to "User"
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | 50.00 % | 80.00 | 0100 | 80.00 % | 13.33 % |
| Test assignment two | 50.00 % | 35.00 | 0100 | 35.00 % | 5.83 % |
| Sub category 1 total | 33.33 % | 115.00 | 0200 | 57.50 % | - |
| Test assignment three | 50.00 % | 100.00 | 0100 | 100.00 % | 16.67 % |
| Test assignment four | 50.00 % | 50.00 | 0100 | 50.00 % | 8.33 % |
| Sub category 2 total | 33.33 % | 150.00 | 0200 | 75.00 % | - |
| Test assignment five | 16.67 % | 21.00 | 0100 | 21.00 % | 3.50 % |
| Test assignment six | 16.67 % | 97.00 | 0100 | 97.00 % | 16.17 % |
| Course total | - | 383.00 | 0600 | 63.83 % | - |
And I log out
And I log in as "student1"
And I am on "C1" course homepage
And I navigate to "User report" in the course gradebook
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
| Test assignment one | 50.00 % | 80.00 | 0100 | 80.00 % | 13.33 % |
| Test assignment two | 50.00 % | 35.00 | 0100 | 35.00 % | 5.83 % |
| Sub category 1 total | 33.33 % | 115.00 | 0200 | 57.50 % | - |
| Test assignment three | 50.00 % | 100.00 | 0100 | 100.00 % | 16.67 % |
| Test assignment four | 50.00 % | 50.00 | 0100 | 50.00 % | 8.33 % |
| Sub category 2 total | 33.33 % | 150.00 | 0200 | 75.00 % | - |
| Test assignment five | 16.67 % | 21.00 | 0100 | 21.00 % | 3.50 % |
| Test assignment six | 16.67 % | 97.00 | 0100 | 97.00 % | 16.17 % |
| Course total | - | 383.00 | 0600 | 63.83 % | - |
@@ -0,0 +1,263 @@
@core @core_grades @gradereport_user @javascript
Feature: Within the User report, a teacher can search for users.
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
| Course 2 | C2 | 0 | 0 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber | phone1 | phone2 | department | institution | city | country |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 | 1234567892 | 1234567893 | ABC1 | ABCD | Perth | AU |
| student1 | Student | 1 | student1@example.com | s1 | 3213078612 | 8974325612 | ABC1 | ABCD | Hanoi | VN |
| student2 | Dummy | User | student2@example.com | s2 | 4365899871 | 7654789012 | ABC2 | ABCD | Tokyo | JP |
| student3 | User | Example | student3@example.com | s3 | 3243249087 | 0875421745 | ABC2 | ABCD | Olney | GB |
| student4 | User | Test | student4@example.com | s4 | 0987532523 | 2149871323 | ABC3 | ABCD | Tokyo | JP |
| student5 | Turtle | Manatee | student5@example.com | s5 | 1239087780 | 9873623589 | ABC3 | ABCD | Perth | AU |
# Note: Add groups etc so we can test that the search ignores those filters as well if we go down the filter dataset path.
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| student4 | C1 | student |
| student5 | C1 | student |
And the following "activities" exist:
| activity | course | idnumber | name |
| assign | C1 | a1 | Test assignment one |
And the following config values are set as admin:
| showuseridentity | idnumber,email,city,country,phone1,phone2,department,institution |
And I change window size to "large"
And I am on the "Course 1" "grades > User report > View" page logged in as "teacher1"
Scenario: A teacher can view and trigger the user search
# Check the placeholder text (no users are initially shown).
Given I should see "Search users"
# Confirm the search is currently inactive and results are unfiltered.
And I should see "Search for a user to view their report"
When I set the field "Search users" to "Turtle"
And "View all results (5)" "option_role" should exist
And I confirm "Turtle Manatee" in "user" search within the gradebook widget exists
And I confirm "User Example" in "user" search within the gradebook widget does not exist
And I click on "Turtle Manatee" "list_item"
# Business case: This will trigger a page reload and can not dynamically update the table.
And I wait until the page is ready
And "Turtle Manatee" "heading" should exist
And "Teacher 1" "heading" should not exist
And "Student 1" "heading" should not exist
And "User Example" "heading" should not exist
And "User Test" "heading" should not exist
And "Dummy User" "heading" should not exist
And I set the field "Search users" to "Turt"
And "View all results (5)" "option_role" should exist
And I click on "Clear search input" "button" in the ".user-search" "css_element"
And "View all results (5)" "option_role" should not be visible
Scenario: A teacher can search the user report to find specified users
# Case: Standard search.
Given I click on "Dummy" in the "user" search widget
And "Dummy User" "heading" should exist
And "Teacher 1" "heading" should not exist
And "Student 1" "heading" should not exist
And "User Example" "heading" should not exist
And "User Test" "heading" should not exist
And "Turtle Manatee" "heading" should not exist
# Case: No users found.
When I set the field "Search users" to "Plagiarism"
And I should see "No results for \"Plagiarism\""
# Table remains unchanged as the user had no results to select from the dropdown.
And "Dummy User" "heading" should exist
And "Teacher 1" "heading" should not exist
And "Student 1" "heading" should not exist
And "User Example" "heading" should not exist
And "User Test" "heading" should not exist
And "Turtle Manatee" "heading" should not exist
# Case: Multiple users found and select only one result.
Then I set the field "Search users" to "User"
And "View all results (5)" "option_role" should exist
And I confirm "Dummy User" in "user" search within the gradebook widget exists
And I confirm "User Example" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
And I confirm "Turtle Manatee" in "user" search within the gradebook widget does not exist
# Check if the matched field names (by lines) includes some identifiable info to help differentiate similar users.
And I confirm "User (student2@example.com)" in "user" search within the gradebook widget exists
And I confirm "User (student3@example.com)" in "user" search within the gradebook widget exists
And I confirm "User (student4@example.com)" in "user" search within the gradebook widget exists
And I click on "Dummy User" "list_item"
And I wait until the page is ready
And "Dummy User" "heading" should exist
And "Teacher 1" "heading" should not exist
And "Student 1" "heading" should not exist
And "User Example" "heading" should not exist
And "User Test" "heading" should not exist
And "Turtle Manatee" "heading" should not exist
# Business case: When searching with multiple partial matches, show the matches in the dropdown + a "View all results for (Bob)"
# Business case cont. When pressing enter with multiple partial matches, behave like when you select the "View all results for (Bob)"
# Case: Multiple users found and select all partial matches.
And I set the field "Search users" to "User"
And "View all results (5)" "option_role" should exist
And I click on "View all results (5)" "option_role"
And I wait until the page is ready
And "Dummy User" "heading" should exist
And "User Example" "heading" should exist
And "User Test" "heading" should exist
And "Student 1" "heading" should exist
And "Turtle Manatee" "heading" should exist
And "Teacher 1" "heading" should not exist
And "Clear" "link" should not exist in the ".user-search" "css_element"
And "Dummy User" "heading" should exist
And "User Example" "heading" should exist
And "User Test" "heading" should exist
And "Student 1" "heading" should exist
And "Turtle Manatee" "heading" should exist
And "Teacher 1" "heading" should not exist
# Case: No users enrolled.
And I am on the "Course 2" "grades > User report > View" page
And I set the field "Search users" to "a"
And I should see "No results for \"a\""
Scenario: A teacher can quickly tell that a search is active on the current table
When I click on "Turtle" in the "user" search widget
# The search input should contain the name of the user we have selected, so that it is clear that the result pertains to a specific user.
Then the field "Search users" matches value "Turtle Manatee"
And I wait until "View all results (5)" "link" does not exist
# Test if we can then further retain the turtle result set and further filter from there.
And I set the field "Search users" to "Turtle plagiarism"
And I wait until "Turtle Manatee" "list_item" does not exist
And I should see "No results for \"Turtle plagiarism\""
Scenario: A teacher can search for values besides the users' name
Given I set the field "Search users" to "student5@example.com"
And "View all results (5)" "option_role" should exist
And "Turtle Manatee" "list_item" should exist
And I set the field "Search users" to "@example.com"
And "View all results (5)" "option_role" should exist
# Note: All learners match this email & showing emails is current default.
And I confirm "Dummy User" in "user" search within the gradebook widget exists
And I confirm "User Example" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Turtle Manatee" in "user" search within the gradebook widget exists
# Search on the country field.
When I set the field "Search users" to "JP"
And "View all results (5)" "option_role" should exist
And I wait until "Turtle Manatee" "list_item" does not exist
And I confirm "Dummy User" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
# Search on the city field.
And I set the field "Search users" to "Hanoi"
And I wait until "User Test" "list_item" does not exist
Then I confirm "Student 1" in "user" search within the gradebook widget exists
# Search on the institution field.
And I set the field "Search users" to "ABCD"
And "Dummy User" "list_item" should exist
And I confirm "User Example" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Turtle Manatee" in "user" search within the gradebook widget exists
# Search on the department field.
And I set the field "Search users" to "ABC3"
And I wait until "User Example" "list_item" does not exist
And I confirm "User Test" in "user" search within the gradebook widget exists
And I confirm "Turtle Manatee" in "user" search within the gradebook widget exists
# Search on the phone1 field.
And I set the field "Search users" to "4365899871"
And I wait until "User Test" "list_item" does not exist
And I confirm "Dummy User" in "user" search within the gradebook widget exists
# Search on the phone2 field.
And I set the field "Search users" to "2149871323"
And I wait until "Dummy User" "list_item" does not exist
And I confirm "User Test" in "user" search within the gradebook widget exists
# Search on the institution field then press enter to show the record set.
And I set the field "Search users" to "ABC"
And "Turtle Manatee" "list_item" should exist
And I confirm "Dummy User" in "user" search within the gradebook widget exists
And I confirm "User Example" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I press the up key
And I press the enter key
And I wait until the page is ready
And "Student 1" "heading" should exist
And "User Example" "heading" should exist
And "User Test" "heading" should exist
And "Dummy User" "heading" should exist
And "Turtle Manatee" "heading" should exist
And "Teacher 1" "heading" should not exist
Scenario: A teacher can only search for fields that he allowed to see
Given the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/course:viewhiddenuserfields | Prohibit | editingteacher | System | |
And the following config values are set as admin:
| hiddenuserfields | email |
And I am on the "Course 1" "grades > User report > View" page logged in as "teacher1"
When I set the field "Search users" to "User"
And "View all results (5)" "option_role" should exist
And I confirm "Dummy User" in "user" search within the gradebook widget exists
And I confirm "User Example" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
# Email is not shown in results.
And I confirm "User" in "user" search within the gradebook widget exists
And I confirm "example.com" in "user" search within the gradebook widget does not exist
# Email is not searchable.
And I set the field "Search users" to "student5@example.com"
And "View all results (5)" "option_role" should not exist
And I confirm "No results for \"student5@example.com\"" in "user" search within the gradebook widget exists
@accessibility
Scenario: A teacher can set focus and search using the input are with a keyboard
Given I set the field "Search users" to "ABC"
# Basic tests for the page.
And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests
# Move onto general keyboard navigation testing.
When "Turtle Manatee" "option_role" should exist
And I press the down key
And ".active" "css_element" should exist in the "Student 1" "option_role"
And I press the up key
And ".active" "css_element" should exist in the "View all results (5)" "option_role"
And I press the down key
And ".active" "css_element" should exist in the "Student 1" "option_role"
And I press the escape key
And the focused element is "Search users" "field"
Then I set the field "Search users" to "Goodmeme"
And I press the down key
And the focused element is "Search users" "field"
And I navigate to "View > User report" in the course gradebook
And I set the field "Search users" to "ABC"
And "Turtle Manatee" "option_role" should exist
And I press the down key
And ".active" "css_element" should exist in the "Student 1" "option_role"
# Lets check the tabbing order.
And I set the field "Search users" to "ABC"
And I wait until "Clear search input" "button" exists
And I click on "Search users" "field"
And I press the tab key
And the focused element is "Clear search input" "button" in the ".user-search" "css_element"
And I press the tab key
And ".groupsearchwidget" "css_element" should exist
# Ensure we can interact with the input & clear search options with the keyboard.
# Space & Enter have the same handling for triggering the two functionalities.
And I set the field "Search users" to "User"
And I press the down key
And I press the enter key
And I wait to be redirected
And "User Example" "heading" should exist
And "Dummy User" "heading" should not exist
And "Student 1" "heading" should not exist
And "User Test" "heading" should not exist
And "Teacher 1" "heading" should not exist
And "Turtle Manatee" "heading" should not exist
@@ -0,0 +1,106 @@
@core @core_grades @gradereport_user @javascript
Feature: We can use the user report
As a user
I browse to the User report
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
Scenario: Verify we can view a user grade report with no users enrolled.
When I am on the "Course 1" "grades > User report > View" page logged in as "admin"
Then I should see "There are no students enrolled in this course."
Scenario: Teacher sees his last viewed user report when navigating back to the gradebook user report.
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| teacher2 | Teacher | 2 | teacher2@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher2 | C1 | editingteacher |
| student1 | C1 | student |
And I am on the "Course 1" "grades > User report > View" page logged in as "teacher1"
And I should see "Search for a user to view their report" in the "region-main" "region"
And I click on "Student 1" in the "user" search widget
And I should see "Student 1" in the "region-main" "region"
When I am on the "Course 1" "grades > User report > View" page
Then I should not see "Search for a user to view their report" in the "region-main" "region"
And I should see "Student 1" in the "region-main" "region"
And I am on the "Course 1" "grades > User report > View" page logged in as "teacher2"
And I should see "Search for a user to view their report" in the "region-main" "region"
Scenario: Teacher sees his last viewed user report if the user is a part of the the current group.
Given the following "groups" exist:
| name | course | idnumber | participation |
| Group 1 | C1 | G1 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "group members" exist:
| user | group |
| student2 | G1 |
And I am on the "Course 1" "grades > User report > View" page logged in as "teacher1"
And I click on "Student 2" in the "user" search widget
And I navigate to "View > Grader report" in the course gradebook
And I click on "Group 1" in the "group" search widget
When I navigate to "View > User report" in the course gradebook
Then I should see "Student 2" in the "region-main" "region"
And I should not see "Search for a user to view their report" in the "region-main" "region"
Scenario: Teacher does not see the last viewed user if the user is not a part of the the current group.
Given the following "groups" exist:
| name | course | idnumber | participation |
| Group 1 | C1 | G1 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "group members" exist:
| user | group |
| student2 | G1 |
And I am on the "Course 1" "grades > User report > View" page logged in as "teacher1"
And I click on "Student 1" in the "user" search widget
And I navigate to "View > Grader report" in the course gradebook
And I click on "Group 1" in the "group" search widget
When I navigate to "View > User report" in the course gradebook
Then I should see "Search for a user to view their report" in the "region-main" "region"
And I should not see "Student 1" in the "region-main" "region"
Scenario: Teacher does not see his last viewed user report if the user is no longer enrolled in the course.
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And I am on the "Course 1" "grades > User report > View" page logged in as "teacher1"
And I click on "Student 1" in the "user" search widget
And I should see "Student 1" in the "region-main" "region"
And I navigate to course participants
And I click on "Unenrol" "icon" in the "Student 1" "table_row"
And I click on "Unenrol" "button" in the "Unenrol" "dialogue"
And I am on "Course 1" course homepage
When I navigate to "View > User report" in the course gradebook
Then I should see "Search for a user to view their report" in the "region-main" "region"
And I should not see "Student 1" in the "region-main" "region"
@@ -0,0 +1,433 @@
<?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 gradereport_user;
use core_external\external_api;
use externallib_advanced_testcase;
use gradereport_user\external\user as user_external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* User grade report functions unit tests
*
* @package gradereport_user
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class externallib_test extends externallib_advanced_testcase {
/**
* Loads some data to be used by the different tests
* @param int $s1grade Student 1 grade
* @param int $s2grade Student 2 grade
* @return array Course and users instances
*/
private function load_data(int $s1grade, int $s2grade, int $s3grade): array {
global $DB;
$course = $this->getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1]);
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$student1 = $this->getDataGenerator()->create_user(['idnumber' => 'testidnumber']);
$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
$student2 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
// Student 3 is in no groups.
$student3 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student3->id, $course->id, $studentrole->id);
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
$teacher = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
$context = \context_course::instance($course->id);
assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $context);
accesslib_clear_all_caches_for_unit_testing();
$group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
groups_add_member($group1->id, $student1->id);
groups_add_member($group1->id, $teacher->id);
groups_add_member($group2->id, $student2->id);
$assignment = $this->getDataGenerator()->create_module('assign', ['name' => "Test assign & grade items", 'course' => $course->id]);
$modcontext = get_coursemodule_from_instance('assign', $assignment->id, $course->id);
$assignment->cmidnumber = $modcontext->id;
$student1grade = ['userid' => $student1->id, 'rawgrade' => $s1grade, 'idnumber' => 'testidnumber1'];
$student2grade = ['userid' => $student2->id, 'rawgrade' => $s2grade, 'idnumber' => 'testidnumber2'];
$student3grade = ['userid' => $student3->id, 'rawgrade' => $s3grade, 'idnumber' => 'testidnumber3'];
$studentgrades = [$student1->id => $student1grade, $student2->id => $student2grade, $student3->id => $student3grade];
assign_grade_item_update($assignment, $studentgrades);
return [
$course,
$teacher,
$student1,
$student2,
$student3,
$assignment
];
}
/**
* Test get_grades_table function case teacher
*/
public function test_get_grades_table_teacher(): void {
$this->resetAfterTest(true);
$s1grade = 80;
$s2grade = 60;
$s3grade = 50;
list($course, $teacher, $student1, $student2, $student3, $assignment) = $this->load_data($s1grade, $s2grade, $s3grade);
// A teacher must see all student grades (in their group only).
$this->setUser($teacher);
$studentgrades = user_external::get_grades_table($course->id);
$studentgrades = external_api::clean_returnvalue(user_external::get_grades_table_returns(), $studentgrades);
// No warnings returned.
$this->assertCount(0, $studentgrades['warnings']);
// Check that only grades for the student in the teacher group are returned.
$this->assertCount(1, $studentgrades['tables']);
// Read returned grades.
$studentreturnedgrades = [];
$studentreturnedgrades[$studentgrades['tables'][0]['userid']] =
(int) $studentgrades['tables'][0]['tabledata'][2]['grade']['content'];
$this->assertEquals($s1grade, $studentreturnedgrades[$student1->id]);
}
/**
* Test get_grades_table function case student
*/
public function test_get_grades_table_student(): void {
global $CFG, $DB;
$this->resetAfterTest(true);
$s1grade = 80;
$s2grade = 60;
$s3grade = 50;
list($course, $teacher, $student1, $student2, $student3, $assignment) = $this->load_data($s1grade, $s2grade, $s3grade);
// A user can see his own grades.
$this->setUser($student1);
$studentgrade = user_external::get_grades_table($course->id, $student1->id);
$studentgrade = external_api::clean_returnvalue(user_external::get_grades_table_returns(), $studentgrade);
// No warnings returned.
$this->assertTrue(count($studentgrade['warnings']) == 0);
$this->assertTrue(count($studentgrade['tables']) == 1);
$student1returnedgrade = (int) $studentgrade['tables'][0]['tabledata'][2]['grade']['content'];
$this->assertEquals($s1grade, $student1returnedgrade);
// A user can see his own even when in no groups.
$this->setUser($student3);
$studentgrade = user_external::get_grades_table($course->id, $student3->id);
$studentgrade = external_api::clean_returnvalue(user_external::get_grades_table_returns(), $studentgrade);
// No warnings returned.
$this->assertTrue(count($studentgrade['warnings']) == 0);
$this->assertTrue(count($studentgrade['tables']) == 1);
$student3returnedgrade = (int) $studentgrade['tables'][0]['tabledata'][2]['grade']['content'];
$this->assertEquals($s3grade, $student3returnedgrade);
// Expect exception when user is not indicated.
$this->setUser($student3);
$this->expectException(\required_capability_exception::class);
user_external::get_grades_table($course->id);
}
/**
* Test get_grades_table function case incorrect permissions
*/
public function test_get_grades_table_permissions(): void {
global $CFG, $DB;
$this->resetAfterTest(true);
$s1grade = 80;
$s2grade = 60;
$s3grade = 50;
list($course, $teacher, $student1, $student2, $student3, $assignment) = $this->load_data($s1grade, $s2grade, $s3grade);
$this->setUser($student2);
try {
$studentgrade = user_external::get_grades_table($course->id, $student1->id);
$this->fail('Exception expected due to not perissions to view other user grades.');
} catch (\moodle_exception $e) {
$this->assertEquals('notingroup', $e->errorcode);
}
}
/**
* Test view_grade_report function
*/
public function test_view_grade_report(): void {
global $USER;
$this->resetAfterTest(true);
$s1grade = 80;
$s2grade = 60;
$s3grade = 50;
list($course, $teacher, $student1, $student2, $student3, $assignment) = $this->load_data($s1grade, $s2grade, $s3grade);
// Redirect events to the sink, so we can recover them later.
$sink = $this->redirectEvents();
$this->setUser($student1);
$result = user_external::view_grade_report($course->id);
$result = external_api::clean_returnvalue(user_external::view_grade_report_returns(), $result);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Check the event details are correct.
$this->assertInstanceOf('\gradereport_user\event\grade_report_viewed', $event);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($USER->id, $event->get_data()['relateduserid']);
$this->setUser($teacher);
$result = user_external::view_grade_report($course->id, $student1->id);
$result = external_api::clean_returnvalue(user_external::view_grade_report_returns(), $result);
$events = $sink->get_events();
$event = reset($events);
$sink->close();
// Check the event details are correct.
$this->assertInstanceOf('\gradereport_user\event\grade_report_viewed', $event);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($student1->id, $event->get_data()['relateduserid']);
$this->setUser($student2);
try {
$studentgrade = user_external::view_grade_report($course->id, $student1->id);
$this->fail('Exception expected due to not permissions to view other user grades.');
} catch (\moodle_exception $e) {
$this->assertEquals('nopermissiontoviewgrades', $e->errorcode);
}
}
/**
* Test get_grades_items function case teacher
*/
public function test_get_grade_items_teacher(): void {
$this->resetAfterTest(true);
$s1grade = 80;
$s2grade = 60;
$s3grade = 50;
list($course, $teacher, $student1, $student2, $student3, $assignment) = $this->load_data($s1grade, $s2grade, $s3grade);
// A teacher must see all student grades (in their group only).
$this->setUser($teacher);
grade_set_setting($course->id, 'report_user_showrank', 1);
grade_set_setting($course->id, 'report_user_showpercentage', 1);
grade_set_setting($course->id, 'report_user_showhiddenitems', 1);
grade_set_setting($course->id, 'report_user_showgrade', 1);
grade_set_setting($course->id, 'report_user_showfeedback', 1);
grade_set_setting($course->id, 'report_user_showweight', 1);
grade_set_setting($course->id, 'report_user_showcontributiontocoursetotal', 1);
grade_set_setting($course->id, 'report_user_showlettergrade', 1);
grade_set_setting($course->id, 'report_user_showaverage', 1);
$studentgrades = user_external::get_grade_items($course->id);
$studentgrades = external_api::clean_returnvalue(user_external::get_grade_items_returns(), $studentgrades);
// No warnings returned.
$this->assertCount(0, $studentgrades['warnings']);
// Check that only grades for the student in the teacher group are returned.
$this->assertCount(1, $studentgrades['usergrades']);
$this->assertCount(2, $studentgrades['usergrades'][0]['gradeitems']);
$this->assertEquals($course->id, $studentgrades['usergrades'][0]['courseid']);
$this->assertEquals($student1->id, $studentgrades['usergrades'][0]['userid']);
// Module grades.
$this->assertEquals($assignment->name, $studentgrades['usergrades'][0]['gradeitems'][0]['itemname']);
$this->assertEquals('mod', $studentgrades['usergrades'][0]['gradeitems'][0]['itemtype']);
$this->assertEquals('assign', $studentgrades['usergrades'][0]['gradeitems'][0]['itemmodule']);
$this->assertEquals($assignment->id, $studentgrades['usergrades'][0]['gradeitems'][0]['iteminstance']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['locked']);
$this->assertEquals($assignment->cmidnumber, $studentgrades['usergrades'][0]['gradeitems'][0]['cmid']);
$this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][0]['itemnumber']);
$this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['outcomeid']);
$this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['scaleid']);
$this->assertEquals(80, $studentgrades['usergrades'][0]['gradeitems'][0]['graderaw']);
$this->assertEquals('80.00', $studentgrades['usergrades'][0]['gradeitems'][0]['gradeformatted']);
$this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][0]['grademin']);
$this->assertEquals(100, $studentgrades['usergrades'][0]['gradeitems'][0]['grademax']);
$this->assertEquals('0&ndash;100', $studentgrades['usergrades'][0]['gradeitems'][0]['rangeformatted']);
$this->assertEquals('80.00 %', $studentgrades['usergrades'][0]['gradeitems'][0]['percentageformatted']);
$this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['feedback']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradehiddenbydate']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeneedsupdate']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeishidden']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeislocked']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeisoverridden']);
$this->assertEquals('B-', $studentgrades['usergrades'][0]['gradeitems'][0]['lettergradeformatted']);
$this->assertEquals(1, $studentgrades['usergrades'][0]['gradeitems'][0]['rank']);
$this->assertEquals(3, $studentgrades['usergrades'][0]['gradeitems'][0]['numusers']);
$this->assertEquals(
round(array_sum([$s1grade, $s2grade, $s3grade]) / 3, 2),
$studentgrades['usergrades'][0]['gradeitems'][0]['averageformatted']);
// Course grades.
$this->assertEquals('course', $studentgrades['usergrades'][0]['gradeitems'][1]['itemtype']);
$this->assertEquals(80, $studentgrades['usergrades'][0]['gradeitems'][1]['graderaw']);
$this->assertEquals('80.00', $studentgrades['usergrades'][0]['gradeitems'][1]['gradeformatted']);
$this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][1]['grademin']);
$this->assertEquals(100, $studentgrades['usergrades'][0]['gradeitems'][1]['grademax']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['locked']);
$this->assertEquals('0&ndash;100', $studentgrades['usergrades'][0]['gradeitems'][1]['rangeformatted']);
$this->assertEquals('80.00 %', $studentgrades['usergrades'][0]['gradeitems'][1]['percentageformatted']);
$this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][1]['feedback']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradehiddenbydate']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradeneedsupdate']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradeishidden']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradeislocked']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][1]['gradeisoverridden']);
$this->assertEquals('B-', $studentgrades['usergrades'][0]['gradeitems'][1]['lettergradeformatted']);
$this->assertEquals(1, $studentgrades['usergrades'][0]['gradeitems'][1]['rank']);
$this->assertEquals(3, $studentgrades['usergrades'][0]['gradeitems'][1]['numusers']);
$this->assertEquals(
round(array_sum([$s1grade, $s2grade, $s3grade]) / 3, 2),
$studentgrades['usergrades'][0]['gradeitems'][1]['averageformatted']);
// Now, override and lock a grade.
$gradegrade = \grade_grade::fetch(['itemid' => $studentgrades['usergrades'][0]['gradeitems'][0]['id'],
'userid' => $studentgrades['usergrades'][0]['userid']]);
$gradegrade->set_overridden(true);
$gradegrade->set_locked(1);
$studentgrades = user_external::get_grade_items($course->id);
$studentgrades = external_api::clean_returnvalue(user_external::get_grade_items_returns(), $studentgrades);
// No warnings returned.
$this->assertCount(0, $studentgrades['warnings']);
// Module grades.
$this->assertTrue($studentgrades['usergrades'][0]['gradeitems'][0]['gradeislocked']);
$this->assertTrue($studentgrades['usergrades'][0]['gradeitems'][0]['gradeisoverridden']);
}
/**
* Test get_grades_items function case student
*/
public function test_get_grade_items_student(): void {
$this->resetAfterTest(true);
$s1grade = 80;
$s2grade = 60;
$s3grade = 50;
list($course, $teacher, $student1, $student2, $student3, $assignment) = $this->load_data($s1grade, $s2grade, $s3grade);
grade_set_setting($course->id, 'report_user_showrank', 1);
grade_set_setting($course->id, 'report_user_showpercentage', 1);
grade_set_setting($course->id, 'report_user_showgrade', 1);
grade_set_setting($course->id, 'report_user_showfeedback', 1);
grade_set_setting($course->id, 'report_user_showweight', 1);
grade_set_setting($course->id, 'report_user_showcontributiontocoursetotal', 1);
grade_set_setting($course->id, 'report_user_showlettergrade', 1);
grade_set_setting($course->id, 'report_user_showaverage', 1);
$this->setUser($student1);
$studentgrades = user_external::get_grade_items($course->id, $student1->id);
$studentgrades = external_api::clean_returnvalue(user_external::get_grade_items_returns(), $studentgrades);
// No warnings returned.
$this->assertCount(0, $studentgrades['warnings']);
// Check that only grades for the student in the teacher group are returned.
$this->assertCount(1, $studentgrades['usergrades']);
$this->assertCount(2, $studentgrades['usergrades'][0]['gradeitems']);
$this->assertEquals($course->id, $studentgrades['usergrades'][0]['courseid']);
$this->assertEquals($student1->id, $studentgrades['usergrades'][0]['userid']);
$this->assertEquals($student1->idnumber, $studentgrades['usergrades'][0]['useridnumber']);
$this->assertEquals($assignment->name, $studentgrades['usergrades'][0]['gradeitems'][0]['itemname']);
$this->assertEquals('mod', $studentgrades['usergrades'][0]['gradeitems'][0]['itemtype']);
$this->assertEquals('assign', $studentgrades['usergrades'][0]['gradeitems'][0]['itemmodule']);
$this->assertEquals($assignment->id, $studentgrades['usergrades'][0]['gradeitems'][0]['iteminstance']);
$this->assertNull($studentgrades['usergrades'][0]['gradeitems'][0]['locked']);
$this->assertEquals($assignment->cmidnumber, $studentgrades['usergrades'][0]['gradeitems'][0]['cmid']);
$this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][0]['itemnumber']);
$this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['outcomeid']);
$this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['scaleid']);
$this->assertEquals(80, $studentgrades['usergrades'][0]['gradeitems'][0]['graderaw']);
$this->assertEquals('80.00', $studentgrades['usergrades'][0]['gradeitems'][0]['gradeformatted']);
$this->assertEquals(0, $studentgrades['usergrades'][0]['gradeitems'][0]['grademin']);
$this->assertEquals(100, $studentgrades['usergrades'][0]['gradeitems'][0]['grademax']);
$this->assertEquals('0&ndash;100', $studentgrades['usergrades'][0]['gradeitems'][0]['rangeformatted']);
$this->assertEquals('80.00 %', $studentgrades['usergrades'][0]['gradeitems'][0]['percentageformatted']);
$this->assertEmpty($studentgrades['usergrades'][0]['gradeitems'][0]['feedback']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradehiddenbydate']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeneedsupdate']);
$this->assertFalse($studentgrades['usergrades'][0]['gradeitems'][0]['gradeishidden']);
$this->assertNull($studentgrades['usergrades'][0]['gradeitems'][0]['gradeislocked']);
$this->assertNull($studentgrades['usergrades'][0]['gradeitems'][0]['gradeisoverridden']);
$this->assertEquals('B-', $studentgrades['usergrades'][0]['gradeitems'][0]['lettergradeformatted']);
$this->assertEquals(1, $studentgrades['usergrades'][0]['gradeitems'][0]['rank']);
$this->assertEquals(3, $studentgrades['usergrades'][0]['gradeitems'][0]['numusers']);
$this->assertEquals(
round(array_sum([$s1grade, $s2grade, $s3grade]) / 3, 2),
$studentgrades['usergrades'][0]['gradeitems'][0]['averageformatted']);
// Check that the idnumber for assignment grades is equal to the cmid.
$this->assertEquals((string) $studentgrades['usergrades'][0]['gradeitems'][0]['cmid'],
$studentgrades['usergrades'][0]['gradeitems'][0]['idnumber']);
// Hide one grade for the user.
$gradegrade = new \grade_grade([
'userid' => $student1->id,
'itemid' => $studentgrades['usergrades'][0]['gradeitems'][0]['id']
], true);
$gradegrade->set_hidden(1);
$studentgrades = user_external::get_grade_items($course->id, $student1->id);
$studentgrades = external_api::clean_returnvalue(user_external::get_grade_items_returns(), $studentgrades);
// Check we get only the course final grade.
$this->assertCount(1, $studentgrades['usergrades']);
$this->assertCount(1, $studentgrades['usergrades'][0]['gradeitems']);
$this->assertEquals('course', $studentgrades['usergrades'][0]['gradeitems'][0]['itemtype']);
}
}
+88
View File
@@ -0,0 +1,88 @@
<?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/>.
/**
* Tests for gradereport_user library functions.
*
* @package gradereport_user
* @copyright 2015 onwards Ankit agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
namespace gradereport_user;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/grade/report/user/lib.php');
/**
* Class gradereport_user_lib_testcase.
*
* @package gradereport_user
* @copyright 2015 onwards Ankit agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class lib_test extends \advanced_testcase {
/**
* @var stdClass The user.
*/
private $user;
/**
* @var stdClass The course.
*/
private $course;
/**
* @var \core_user\output\myprofile\tree The navigation tree.
*/
private $tree;
public function setUp(): void {
$this->user = $this->getDataGenerator()->create_user();
$this->course = $this->getDataGenerator()->create_course();
$this->tree = new \core_user\output\myprofile\tree();
$this->resetAfterTest();
}
/**
* Tests the gradereport_user_myprofile_navigation() function.
*/
public function test_gradereport_user_myprofile_navigation(): void {
$this->setAdminUser();
$iscurrentuser = false;
gradereport_user_myprofile_navigation($this->tree, $this->user, $iscurrentuser, $this->course);
$reflector = new \ReflectionObject($this->tree);
$nodes = $reflector->getProperty('nodes');
$this->assertArrayHasKey('grade', $nodes->getValue($this->tree));
}
/**
* Tests the gradereport_user_myprofile_navigation() function for a user
* without permission to view the grade node.
*/
public function test_gradereport_user_myprofile_navigation_without_permission(): void {
$this->setUser($this->user);
$iscurrentuser = true;
gradereport_user_myprofile_navigation($this->tree, $this->user, $iscurrentuser, $this->course);
$reflector = new \ReflectionObject($this->tree);
$nodes = $reflector->getProperty('nodes');
$this->assertArrayNotHasKey('grade', $nodes->getValue($this->tree));
}
}
@@ -0,0 +1,81 @@
<?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/>.
/**
* Unit tests for the gradereport_user implementation of the privacy API.
*
* @package gradereport_user
* @category test
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradereport_user\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\request\writer;
/**
* Unit tests for the gradereport_user implementation of the privacy API.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/**
* Basic setup for these tests.
*/
public function setUp(): void {
$this->resetAfterTest(true);
}
/**
* 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.
* These preferences can be set on each course, but the value is shared in the whole site.
*/
public function test_export_user_preferences_single(): void {
// Define a user preference.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
set_user_preference('gradereport_user_view_user', 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('gradereport_user');
$this->assertCount(1, (array) $prefs);
$this->assertEquals(
get_string('privacy:metadata:preference:gradereport_user_view_user', 'gradereport_user'),
$prefs->gradereport_user_view_user->description
);
$this->assertEquals(get_string('yes'), $prefs->gradereport_user_view_user->value);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?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/>.
/**
* Version details for the user gradebook report
*
* @package gradereport_user
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'gradereport_user'; // Full name of the plugin (used for diagnostics)