From ca77344eadc0e68b49255b65bfbd7bfe84445190 Mon Sep 17 00:00:00 2001 From: Mihail Geshoski Date: Fri, 30 Sep 2022 02:09:35 +0800 Subject: [PATCH] MDL-68437 forum: Support to show only active course users in grader The following change adds support to show only active course users in the forum grader and generally replicates the behaviour from the assignment grader which relies on certain config settings, user permissions and capabilities. --- mod/forum/amd/build/grades/grader.min.js | 2 +- mod/forum/amd/build/grades/grader.min.js.map | 2 +- mod/forum/amd/src/grades/grader.js | 8 +- mod/forum/classes/grades/forum_gradeitem.php | 17 ++++ .../local/renderers/discussion_list.php | 1 + .../templates/grades/grade_button.mustache | 1 + .../tests/grades_forum_gradeitem_test.php | 85 +++++++++++++++++++ mod/forum/view.php | 1 + 8 files changed, 112 insertions(+), 5 deletions(-) diff --git a/mod/forum/amd/build/grades/grader.min.js b/mod/forum/amd/build/grades/grader.min.js index 6de71f2efa8..2e0c3cf6510 100644 --- a/mod/forum/amd/build/grades/grader.min.js +++ b/mod/forum/amd/build/grades/grader.min.js @@ -5,6 +5,6 @@ define("mod_forum/grades/grader",["exports","./grader/selectors","mod_forum/repo * @module mod_forum/grades/grader * @copyright 2019 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.registerLaunchListeners=void 0,Selectors=_interopRequireWildcard(Selectors),_repository=_interopRequireDefault(_repository),_templates=_interopRequireDefault(_templates),Grader=_interopRequireWildcard(Grader),_notification=_interopRequireDefault(_notification),_repository2=_interopRequireDefault(_repository2);const templateNames_contentRegion="mod_forum/grades/grader/discussion/posts",getContentForUserIdFunction=(cmid,experimentalDisplayMode)=>userid=>_repository.default.getDiscussionByUserID(userid,cmid).then((context=>(context.discussions=context.discussions.map(discussionPostMapper),context.experimentaldisplaymode=!!experimentalDisplayMode,_templates.default.render(templateNames_contentRegion,context)))).catch(_notification.default.exception),getUsersForCmidFunction=(cmid,groupID)=>async()=>(await _repository2.default.getUsersFromCourseModuleID(cmid,groupID)).users,findGradableNode=node=>node.closest(Selectors.gradableItem),discussionPostMapper=discussion=>{const parentMap=new Map;discussion.posts.parentposts.forEach((post=>parentMap.set(post.id,post)));const userPosts=discussion.posts.userposts.map((post=>{post.readonly=!0,post.hasreplies=!1,post.replies=[];const parent=post.parentid?parentMap.get(post.parentid):null;return parent&&(parent.hasreplies=!1,parent.replies=[],parent.readonly=!0,post.parentauthorname=parent.author.fullname),{parent:parent,post:post}}));return{...discussion,posts:userPosts}};_exports.registerLaunchListeners=()=>{document.addEventListener("click",(async e=>{if(e.target.matches(Selectors.launch)){const rootNode=findGradableNode(e.target);if(!rootNode)throw Error("Unable to find a gradable item");if(!rootNode.matches(Selectors.gradableItems.wholeForum))throw Error("Unable to find a valid gradable item");e.preventDefault();try{await async function(rootNode){let{focusOnClose:focusOnClose=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const data=rootNode.dataset,gradingPanelFunctions=await Grader.getGradingPanelFunctions("mod_forum",data.contextid,data.gradingComponent,data.gradingComponentSubtype,data.gradableItemtype),groupID=data.group?data.group:0;await Grader.launch(getUsersForCmidFunction(data.cmid,groupID),getContentForUserIdFunction(data.cmid,"1"==data.experimentalDisplayMode),gradingPanelFunctions.getter,gradingPanelFunctions.setter,{groupid:data.groupid,initialUserId:data.initialuserid,moduleName:data.name,courseName:data.courseName,courseUrl:(0,_url.relativeUrl)("/course/view.php",{id:data.courseId}),sendStudentNotifications:data.sendStudentNotifications,focusOnClose:focusOnClose})}(rootNode,{focusOnClose:e.target})}catch(error){_notification.default.exception(error)}}if(e.target.matches(Selectors.viewGrade)){e.preventDefault();const rootNode=findGradableNode(e.target);if(!rootNode)throw Error("Unable to find a gradable item");if(!rootNode.matches(Selectors.gradableItems.wholeForum))throw Error("Unable to find a valid gradable item");e.preventDefault();try{await async function(rootNode){let{focusOnClose:focusOnClose=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const data=rootNode.dataset,gradingPanelFunctions=await Grader.getGradingPanelFunctions("mod_forum",data.contextid,data.gradingComponent,data.gradingComponentSubtype,data.gradableItemtype);await Grader.view(gradingPanelFunctions.getter,data.userid,data.name,{focusOnClose:focusOnClose})}(rootNode,{focusOnClose:e.target})}catch(error){_notification.default.exception(error)}}}))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.registerLaunchListeners=void 0,Selectors=_interopRequireWildcard(Selectors),_repository=_interopRequireDefault(_repository),_templates=_interopRequireDefault(_templates),Grader=_interopRequireWildcard(Grader),_notification=_interopRequireDefault(_notification),_repository2=_interopRequireDefault(_repository2);const templateNames_contentRegion="mod_forum/grades/grader/discussion/posts",getContentForUserIdFunction=(cmid,experimentalDisplayMode)=>userid=>_repository.default.getDiscussionByUserID(userid,cmid).then((context=>(context.discussions=context.discussions.map(discussionPostMapper),context.experimentaldisplaymode=!!experimentalDisplayMode,_templates.default.render(templateNames_contentRegion,context)))).catch(_notification.default.exception),getUsersForCmidFunction=(cmid,groupID,onlyActive)=>async()=>(await _repository2.default.getUsersFromCourseModuleID(cmid,groupID,onlyActive)).users,findGradableNode=node=>node.closest(Selectors.gradableItem),discussionPostMapper=discussion=>{const parentMap=new Map;discussion.posts.parentposts.forEach((post=>parentMap.set(post.id,post)));const userPosts=discussion.posts.userposts.map((post=>{post.readonly=!0,post.hasreplies=!1,post.replies=[];const parent=post.parentid?parentMap.get(post.parentid):null;return parent&&(parent.hasreplies=!1,parent.replies=[],parent.readonly=!0,post.parentauthorname=parent.author.fullname),{parent:parent,post:post}}));return{...discussion,posts:userPosts}};_exports.registerLaunchListeners=()=>{document.addEventListener("click",(async e=>{if(e.target.matches(Selectors.launch)){const rootNode=findGradableNode(e.target);if(!rootNode)throw Error("Unable to find a gradable item");if(!rootNode.matches(Selectors.gradableItems.wholeForum))throw Error("Unable to find a valid gradable item");e.preventDefault();try{await async function(rootNode){let{focusOnClose:focusOnClose=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const data=rootNode.dataset,gradingPanelFunctions=await Grader.getGradingPanelFunctions("mod_forum",data.contextid,data.gradingComponent,data.gradingComponentSubtype,data.gradableItemtype),groupID=data.group?data.group:0,onlyActive=data.gradeOnlyActiveUsers;await Grader.launch(getUsersForCmidFunction(data.cmid,groupID,onlyActive),getContentForUserIdFunction(data.cmid,"1"==data.experimentalDisplayMode),gradingPanelFunctions.getter,gradingPanelFunctions.setter,{groupid:data.groupid,initialUserId:data.initialuserid,moduleName:data.name,courseName:data.courseName,courseUrl:(0,_url.relativeUrl)("/course/view.php",{id:data.courseId}),sendStudentNotifications:data.sendStudentNotifications,focusOnClose:focusOnClose})}(rootNode,{focusOnClose:e.target})}catch(error){_notification.default.exception(error)}}if(e.target.matches(Selectors.viewGrade)){e.preventDefault();const rootNode=findGradableNode(e.target);if(!rootNode)throw Error("Unable to find a gradable item");if(!rootNode.matches(Selectors.gradableItems.wholeForum))throw Error("Unable to find a valid gradable item");e.preventDefault();try{await async function(rootNode){let{focusOnClose:focusOnClose=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const data=rootNode.dataset,gradingPanelFunctions=await Grader.getGradingPanelFunctions("mod_forum",data.contextid,data.gradingComponent,data.gradingComponentSubtype,data.gradableItemtype);await Grader.view(gradingPanelFunctions.getter,data.userid,data.name,{focusOnClose:focusOnClose})}(rootNode,{focusOnClose:e.target})}catch(error){_notification.default.exception(error)}}}))}})); //# sourceMappingURL=grader.min.js.map \ No newline at end of file diff --git a/mod/forum/amd/build/grades/grader.min.js.map b/mod/forum/amd/build/grades/grader.min.js.map index 61aab8f47c0..b2e5cda5e3a 100644 --- a/mod/forum/amd/build/grades/grader.min.js.map +++ b/mod/forum/amd/build/grades/grader.min.js.map @@ -1 +1 @@ -{"version":3,"file":"grader.min.js","sources":["../../src/grades/grader.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 .\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/grader\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Selectors from './grader/selectors';\nimport Repository from 'mod_forum/repository';\nimport Templates from 'core/templates';\nimport * as Grader from '../local/grades/grader';\nimport Notification from 'core/notification';\nimport CourseRepository from 'core_course/repository';\nimport {relativeUrl} from 'core/url';\n\nconst templateNames = {\n contentRegion: 'mod_forum/grades/grader/discussion/posts',\n};\n\n/**\n * Curried function with CMID set, this is then used in unified grader as a fetch a users content.\n *\n * @param {Number} cmid\n * @param {Bool} experimentalDisplayMode\n * @return {Function}\n */\nconst getContentForUserIdFunction = (cmid, experimentalDisplayMode) => (userid) => {\n /**\n * Given the parent function is called with the second param set execute the partially executed function.\n *\n * @param {Number} userid\n */\n return Repository.getDiscussionByUserID(userid, cmid)\n .then(context => {\n // Rebuild the returned data for the template.\n context.discussions = context.discussions.map(discussionPostMapper);\n context.experimentaldisplaymode = experimentalDisplayMode ? true : false;\n\n return Templates.render(templateNames.contentRegion, context);\n })\n .catch(Notification.exception);\n};\n\n/**\n * Curried function with CMID set, this is then used in unified grader as a fetch users call.\n * The function curried fetches all users in a course for a given CMID.\n *\n * @param {Number} cmid\n * @param {Number} groupID\n * @return {Array} Array of users for a given context.\n */\nconst getUsersForCmidFunction = (cmid, groupID) => async() => {\n const context = await CourseRepository.getUsersFromCourseModuleID(cmid, groupID);\n\n return context.users;\n};\n\n\nconst findGradableNode = node => node.closest(Selectors.gradableItem);\n\n/**\n * For a discussion we need to manipulate it's posts to hide certain UI elements.\n *\n * @param {Object} discussion\n * @return {Array} name, id, posts\n */\nconst discussionPostMapper = (discussion) => {\n // Map postid => post.\n const parentMap = new Map();\n discussion.posts.parentposts.forEach(post => parentMap.set(post.id, post));\n const userPosts = discussion.posts.userposts.map(post => {\n post.readonly = true;\n post.hasreplies = false;\n post.replies = [];\n\n const parent = post.parentid ? parentMap.get(post.parentid) : null;\n if (parent) {\n parent.hasreplies = false;\n parent.replies = [];\n parent.readonly = true;\n post.parentauthorname = parent.author.fullname;\n }\n\n return {\n parent,\n post\n };\n });\n\n return {\n ...discussion,\n posts: userPosts,\n };\n};\n\n/**\n * Launch the Grader.\n *\n * @param {HTMLElement} rootNode the root HTML element describing what is to be graded\n * @param {object} param\n * @param {bool} [param.focusOnClose=null]\n */\nconst launchWholeForumGrading = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const data = rootNode.dataset;\n const gradingPanelFunctions = await Grader.getGradingPanelFunctions(\n 'mod_forum',\n data.contextid,\n data.gradingComponent,\n data.gradingComponentSubtype,\n data.gradableItemtype\n );\n\n const groupID = data.group ? data.group : 0;\n\n await Grader.launch(\n getUsersForCmidFunction(data.cmid, groupID),\n getContentForUserIdFunction(data.cmid, data.experimentalDisplayMode == \"1\"),\n gradingPanelFunctions.getter,\n gradingPanelFunctions.setter,\n {\n groupid: data.groupid,\n initialUserId: data.initialuserid,\n moduleName: data.name,\n courseName: data.courseName,\n courseUrl: relativeUrl('/course/view.php', {id: data.courseId}),\n sendStudentNotifications: data.sendStudentNotifications,\n focusOnClose,\n }\n );\n};\n\n/**\n * Launch the Grader.\n *\n * @param {HTMLElement} rootNode the root HTML element describing what is to be graded\n * @param {object} param\n * @param {bool} [param.focusOnClose=null]\n */\nconst launchViewGrading = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const data = rootNode.dataset;\n const gradingPanelFunctions = await Grader.getGradingPanelFunctions(\n 'mod_forum',\n data.contextid,\n data.gradingComponent,\n data.gradingComponentSubtype,\n data.gradableItemtype\n );\n\n await Grader.view(\n gradingPanelFunctions.getter,\n data.userid,\n data.name,\n {\n focusOnClose,\n }\n );\n};\n\n/**\n * Register listeners to launch the grading panel.\n */\nexport const registerLaunchListeners = () => {\n document.addEventListener('click', async(e) => {\n if (e.target.matches(Selectors.launch)) {\n const rootNode = findGradableNode(e.target);\n\n if (!rootNode) {\n throw Error('Unable to find a gradable item');\n }\n\n if (rootNode.matches(Selectors.gradableItems.wholeForum)) {\n // Note: The preventDefault must be before any async function calls because the function becomes async\n // at that point and the default action is implemented.\n e.preventDefault();\n try {\n await launchWholeForumGrading(rootNode, {\n focusOnClose: e.target,\n });\n } catch (error) {\n Notification.exception(error);\n }\n } else {\n throw Error('Unable to find a valid gradable item');\n }\n }\n if (e.target.matches(Selectors.viewGrade)) {\n e.preventDefault();\n const rootNode = findGradableNode(e.target);\n\n if (!rootNode) {\n throw Error('Unable to find a gradable item');\n }\n\n if (rootNode.matches(Selectors.gradableItems.wholeForum)) {\n // Note: The preventDefault must be before any async function calls because the function becomes async\n // at that point and the default action is implemented.\n e.preventDefault();\n try {\n await launchViewGrading(rootNode, {\n focusOnClose: e.target,\n });\n } catch (error) {\n Notification.exception(error);\n }\n } else {\n throw Error('Unable to find a valid gradable item');\n }\n }\n });\n};\n"],"names":["templateNames","getContentForUserIdFunction","cmid","experimentalDisplayMode","userid","Repository","getDiscussionByUserID","then","context","discussions","map","discussionPostMapper","experimentaldisplaymode","Templates","render","catch","Notification","exception","getUsersForCmidFunction","groupID","async","CourseRepository","getUsersFromCourseModuleID","users","findGradableNode","node","closest","Selectors","gradableItem","discussion","parentMap","Map","posts","parentposts","forEach","post","set","id","userPosts","userposts","readonly","hasreplies","replies","parent","parentid","get","parentauthorname","author","fullname","document","addEventListener","e","target","matches","launch","rootNode","Error","gradableItems","wholeForum","preventDefault","focusOnClose","data","dataset","gradingPanelFunctions","Grader","getGradingPanelFunctions","contextid","gradingComponent","gradingComponentSubtype","gradableItemtype","group","getter","setter","groupid","initialUserId","initialuserid","moduleName","name","courseName","courseUrl","courseId","sendStudentNotifications","launchWholeForumGrading","error","viewGrade","view","launchViewGrading"],"mappings":";;;;;;;mYA8BMA,4BACa,2CAUbC,4BAA8B,CAACC,KAAMC,0BAA6BC,QAM7DC,oBAAWC,sBAAsBF,OAAQF,MAC3CK,MAAKC,UAEFA,QAAQC,YAAcD,QAAQC,YAAYC,IAAIC,sBAC9CH,QAAQI,0BAA0BT,wBAE3BU,mBAAUC,OAAOd,4BAA6BQ,YAExDO,MAAMC,sBAAaC,WAWtBC,wBAA0B,CAAChB,KAAMiB,UAAYC,gBACzBC,qBAAiBC,2BAA2BpB,KAAMiB,UAEzDI,MAIbC,iBAAmBC,MAAQA,KAAKC,QAAQC,UAAUC,cAQlDjB,qBAAwBkB,mBAEpBC,UAAY,IAAIC,IACtBF,WAAWG,MAAMC,YAAYC,SAAQC,MAAQL,UAAUM,IAAID,KAAKE,GAAIF,cAC9DG,UAAYT,WAAWG,MAAMO,UAAU7B,KAAIyB,OAC7CA,KAAKK,UAAW,EAChBL,KAAKM,YAAa,EAClBN,KAAKO,QAAU,SAETC,OAASR,KAAKS,SAAWd,UAAUe,IAAIV,KAAKS,UAAY,YAC1DD,SACAA,OAAOF,YAAa,EACpBE,OAAOD,QAAU,GACjBC,OAAOH,UAAW,EAClBL,KAAKW,iBAAmBH,OAAOI,OAAOC,UAGnC,CACHL,OAAAA,OACAR,KAAAA,eAID,IACAN,WACHG,MAAOM,6CA0EwB,KACnCW,SAASC,iBAAiB,SAAS9B,MAAAA,OAC3B+B,EAAEC,OAAOC,QAAQ1B,UAAU2B,QAAS,OAC9BC,SAAW/B,iBAAiB2B,EAAEC,YAE/BG,eACKC,MAAM,sCAGZD,SAASF,QAAQ1B,UAAU8B,cAAcC,kBAYnCF,MAAM,wCATZL,EAAEQ,2BA3EcvC,eAAMmC,cAAUK,aAC5CA,aAAe,6DACf,SACMC,KAAON,SAASO,QAChBC,4BAA8BC,OAAOC,yBACvC,YACAJ,KAAKK,UACLL,KAAKM,iBACLN,KAAKO,wBACLP,KAAKQ,kBAGHlD,QAAU0C,KAAKS,MAAQT,KAAKS,MAAQ,QAEpCN,OAAOV,OACTpC,wBAAwB2C,KAAK3D,KAAMiB,SACnClB,4BAA4B4D,KAAK3D,KAAsC,KAAhC2D,KAAK1D,yBAC5C4D,sBAAsBQ,OACtBR,sBAAsBS,OACtB,CACIC,QAASZ,KAAKY,QACdC,cAAeb,KAAKc,cACpBC,WAAYf,KAAKgB,KACjBC,WAAYjB,KAAKiB,WACjBC,WAAW,oBAAY,mBAAoB,CAAC1C,GAAIwB,KAAKmB,WACrDC,yBAA0BpB,KAAKoB,yBAC/BrB,aAAAA,eAmDcsB,CAAwB3B,SAAU,CACpCK,aAAcT,EAAEC,SAEtB,MAAO+B,6BACQlE,UAAUkE,WAM/BhC,EAAEC,OAAOC,QAAQ1B,UAAUyD,WAAY,CACvCjC,EAAEQ,uBACIJ,SAAW/B,iBAAiB2B,EAAEC,YAE/BG,eACKC,MAAM,sCAGZD,SAASF,QAAQ1B,UAAU8B,cAAcC,kBAYnCF,MAAM,wCATZL,EAAEQ,2BA5DQvC,eAAMmC,cAAUK,aACtCA,aAAe,6DACf,SACMC,KAAON,SAASO,QAChBC,4BAA8BC,OAAOC,yBACvC,YACAJ,KAAKK,UACLL,KAAKM,iBACLN,KAAKO,wBACLP,KAAKQ,wBAGHL,OAAOqB,KACTtB,sBAAsBQ,OACtBV,KAAKzD,OACLyD,KAAKgB,KACL,CACIjB,aAAAA,eA6Cc0B,CAAkB/B,SAAU,CAC9BK,aAAcT,EAAEC,SAEtB,MAAO+B,6BACQlE,UAAUkE"} \ No newline at end of file +{"version":3,"file":"grader.min.js","sources":["../../src/grades/grader.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 .\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/grader\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Selectors from './grader/selectors';\nimport Repository from 'mod_forum/repository';\nimport Templates from 'core/templates';\nimport * as Grader from '../local/grades/grader';\nimport Notification from 'core/notification';\nimport CourseRepository from 'core_course/repository';\nimport {relativeUrl} from 'core/url';\n\nconst templateNames = {\n contentRegion: 'mod_forum/grades/grader/discussion/posts',\n};\n\n/**\n * Curried function with CMID set, this is then used in unified grader as a fetch a users content.\n *\n * @param {Number} cmid\n * @param {Bool} experimentalDisplayMode\n * @return {Function}\n */\nconst getContentForUserIdFunction = (cmid, experimentalDisplayMode) => (userid) => {\n /**\n * Given the parent function is called with the second param set execute the partially executed function.\n *\n * @param {Number} userid\n */\n return Repository.getDiscussionByUserID(userid, cmid)\n .then(context => {\n // Rebuild the returned data for the template.\n context.discussions = context.discussions.map(discussionPostMapper);\n context.experimentaldisplaymode = experimentalDisplayMode ? true : false;\n\n return Templates.render(templateNames.contentRegion, context);\n })\n .catch(Notification.exception);\n};\n\n/**\n * Curried function with CMID set, this is then used in unified grader as a fetch users call.\n * The function curried fetches all users in a course for a given CMID.\n *\n * @param {Number} cmid\n * @param {Number} groupID\n * @param {Boolean} onlyActive Whether to fetch only the active enrolled users or all enrolled users in the course.\n * @return {Array} Array of users for a given context.\n */\nconst getUsersForCmidFunction = (cmid, groupID, onlyActive) => async() => {\n const context = await CourseRepository.getUsersFromCourseModuleID(cmid, groupID, onlyActive);\n\n return context.users;\n};\n\n\nconst findGradableNode = node => node.closest(Selectors.gradableItem);\n\n/**\n * For a discussion we need to manipulate it's posts to hide certain UI elements.\n *\n * @param {Object} discussion\n * @return {Array} name, id, posts\n */\nconst discussionPostMapper = (discussion) => {\n // Map postid => post.\n const parentMap = new Map();\n discussion.posts.parentposts.forEach(post => parentMap.set(post.id, post));\n const userPosts = discussion.posts.userposts.map(post => {\n post.readonly = true;\n post.hasreplies = false;\n post.replies = [];\n\n const parent = post.parentid ? parentMap.get(post.parentid) : null;\n if (parent) {\n parent.hasreplies = false;\n parent.replies = [];\n parent.readonly = true;\n post.parentauthorname = parent.author.fullname;\n }\n\n return {\n parent,\n post\n };\n });\n\n return {\n ...discussion,\n posts: userPosts,\n };\n};\n\n/**\n * Launch the Grader.\n *\n * @param {HTMLElement} rootNode the root HTML element describing what is to be graded\n * @param {object} param\n * @param {bool} [param.focusOnClose=null]\n */\nconst launchWholeForumGrading = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const data = rootNode.dataset;\n const gradingPanelFunctions = await Grader.getGradingPanelFunctions(\n 'mod_forum',\n data.contextid,\n data.gradingComponent,\n data.gradingComponentSubtype,\n data.gradableItemtype\n );\n\n const groupID = data.group ? data.group : 0;\n const onlyActive = data.gradeOnlyActiveUsers;\n\n await Grader.launch(\n getUsersForCmidFunction(data.cmid, groupID, onlyActive),\n getContentForUserIdFunction(data.cmid, data.experimentalDisplayMode == \"1\"),\n gradingPanelFunctions.getter,\n gradingPanelFunctions.setter,\n {\n groupid: data.groupid,\n initialUserId: data.initialuserid,\n moduleName: data.name,\n courseName: data.courseName,\n courseUrl: relativeUrl('/course/view.php', {id: data.courseId}),\n sendStudentNotifications: data.sendStudentNotifications,\n focusOnClose,\n }\n );\n};\n\n/**\n * Launch the Grader.\n *\n * @param {HTMLElement} rootNode the root HTML element describing what is to be graded\n * @param {object} param\n * @param {bool} [param.focusOnClose=null]\n */\nconst launchViewGrading = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const data = rootNode.dataset;\n const gradingPanelFunctions = await Grader.getGradingPanelFunctions(\n 'mod_forum',\n data.contextid,\n data.gradingComponent,\n data.gradingComponentSubtype,\n data.gradableItemtype\n );\n\n await Grader.view(\n gradingPanelFunctions.getter,\n data.userid,\n data.name,\n {\n focusOnClose,\n }\n );\n};\n\n/**\n * Register listeners to launch the grading panel.\n */\nexport const registerLaunchListeners = () => {\n document.addEventListener('click', async(e) => {\n if (e.target.matches(Selectors.launch)) {\n const rootNode = findGradableNode(e.target);\n\n if (!rootNode) {\n throw Error('Unable to find a gradable item');\n }\n\n if (rootNode.matches(Selectors.gradableItems.wholeForum)) {\n // Note: The preventDefault must be before any async function calls because the function becomes async\n // at that point and the default action is implemented.\n e.preventDefault();\n try {\n await launchWholeForumGrading(rootNode, {\n focusOnClose: e.target,\n });\n } catch (error) {\n Notification.exception(error);\n }\n } else {\n throw Error('Unable to find a valid gradable item');\n }\n }\n if (e.target.matches(Selectors.viewGrade)) {\n e.preventDefault();\n const rootNode = findGradableNode(e.target);\n\n if (!rootNode) {\n throw Error('Unable to find a gradable item');\n }\n\n if (rootNode.matches(Selectors.gradableItems.wholeForum)) {\n // Note: The preventDefault must be before any async function calls because the function becomes async\n // at that point and the default action is implemented.\n e.preventDefault();\n try {\n await launchViewGrading(rootNode, {\n focusOnClose: e.target,\n });\n } catch (error) {\n Notification.exception(error);\n }\n } else {\n throw Error('Unable to find a valid gradable item');\n }\n }\n });\n};\n"],"names":["templateNames","getContentForUserIdFunction","cmid","experimentalDisplayMode","userid","Repository","getDiscussionByUserID","then","context","discussions","map","discussionPostMapper","experimentaldisplaymode","Templates","render","catch","Notification","exception","getUsersForCmidFunction","groupID","onlyActive","async","CourseRepository","getUsersFromCourseModuleID","users","findGradableNode","node","closest","Selectors","gradableItem","discussion","parentMap","Map","posts","parentposts","forEach","post","set","id","userPosts","userposts","readonly","hasreplies","replies","parent","parentid","get","parentauthorname","author","fullname","document","addEventListener","e","target","matches","launch","rootNode","Error","gradableItems","wholeForum","preventDefault","focusOnClose","data","dataset","gradingPanelFunctions","Grader","getGradingPanelFunctions","contextid","gradingComponent","gradingComponentSubtype","gradableItemtype","group","gradeOnlyActiveUsers","getter","setter","groupid","initialUserId","initialuserid","moduleName","name","courseName","courseUrl","courseId","sendStudentNotifications","launchWholeForumGrading","error","viewGrade","view","launchViewGrading"],"mappings":";;;;;;;mYA8BMA,4BACa,2CAUbC,4BAA8B,CAACC,KAAMC,0BAA6BC,QAM7DC,oBAAWC,sBAAsBF,OAAQF,MAC3CK,MAAKC,UAEFA,QAAQC,YAAcD,QAAQC,YAAYC,IAAIC,sBAC9CH,QAAQI,0BAA0BT,wBAE3BU,mBAAUC,OAAOd,4BAA6BQ,YAExDO,MAAMC,sBAAaC,WAYtBC,wBAA0B,CAAChB,KAAMiB,QAASC,aAAeC,gBACrCC,qBAAiBC,2BAA2BrB,KAAMiB,QAASC,aAElEI,MAIbC,iBAAmBC,MAAQA,KAAKC,QAAQC,UAAUC,cAQlDlB,qBAAwBmB,mBAEpBC,UAAY,IAAIC,IACtBF,WAAWG,MAAMC,YAAYC,SAAQC,MAAQL,UAAUM,IAAID,KAAKE,GAAIF,cAC9DG,UAAYT,WAAWG,MAAMO,UAAU9B,KAAI0B,OAC7CA,KAAKK,UAAW,EAChBL,KAAKM,YAAa,EAClBN,KAAKO,QAAU,SAETC,OAASR,KAAKS,SAAWd,UAAUe,IAAIV,KAAKS,UAAY,YAC1DD,SACAA,OAAOF,YAAa,EACpBE,OAAOD,QAAU,GACjBC,OAAOH,UAAW,EAClBL,KAAKW,iBAAmBH,OAAOI,OAAOC,UAGnC,CACHL,OAAAA,OACAR,KAAAA,eAID,IACAN,WACHG,MAAOM,6CA2EwB,KACnCW,SAASC,iBAAiB,SAAS9B,MAAAA,OAC3B+B,EAAEC,OAAOC,QAAQ1B,UAAU2B,QAAS,OAC9BC,SAAW/B,iBAAiB2B,EAAEC,YAE/BG,eACKC,MAAM,sCAGZD,SAASF,QAAQ1B,UAAU8B,cAAcC,kBAYnCF,MAAM,wCATZL,EAAEQ,2BA5EcvC,eAAMmC,cAAUK,aAC5CA,aAAe,6DACf,SACMC,KAAON,SAASO,QAChBC,4BAA8BC,OAAOC,yBACvC,YACAJ,KAAKK,UACLL,KAAKM,iBACLN,KAAKO,wBACLP,KAAKQ,kBAGHnD,QAAU2C,KAAKS,MAAQT,KAAKS,MAAQ,EACpCnD,WAAa0C,KAAKU,2BAElBP,OAAOV,OACTrC,wBAAwB4C,KAAK5D,KAAMiB,QAASC,YAC5CnB,4BAA4B6D,KAAK5D,KAAsC,KAAhC4D,KAAK3D,yBAC5C6D,sBAAsBS,OACtBT,sBAAsBU,OACtB,CACIC,QAASb,KAAKa,QACdC,cAAed,KAAKe,cACpBC,WAAYhB,KAAKiB,KACjBC,WAAYlB,KAAKkB,WACjBC,WAAW,oBAAY,mBAAoB,CAAC3C,GAAIwB,KAAKoB,WACrDC,yBAA0BrB,KAAKqB,yBAC/BtB,aAAAA,eAmDcuB,CAAwB5B,SAAU,CACpCK,aAAcT,EAAEC,SAEtB,MAAOgC,6BACQpE,UAAUoE,WAM/BjC,EAAEC,OAAOC,QAAQ1B,UAAU0D,WAAY,CACvClC,EAAEQ,uBACIJ,SAAW/B,iBAAiB2B,EAAEC,YAE/BG,eACKC,MAAM,sCAGZD,SAASF,QAAQ1B,UAAU8B,cAAcC,kBAYnCF,MAAM,wCATZL,EAAEQ,2BA5DQvC,eAAMmC,cAAUK,aACtCA,aAAe,6DACf,SACMC,KAAON,SAASO,QAChBC,4BAA8BC,OAAOC,yBACvC,YACAJ,KAAKK,UACLL,KAAKM,iBACLN,KAAKO,wBACLP,KAAKQ,wBAGHL,OAAOsB,KACTvB,sBAAsBS,OACtBX,KAAK1D,OACL0D,KAAKiB,KACL,CACIlB,aAAAA,eA6Cc2B,CAAkBhC,SAAU,CAC9BK,aAAcT,EAAEC,SAEtB,MAAOgC,6BACQpE,UAAUoE"} \ No newline at end of file diff --git a/mod/forum/amd/src/grades/grader.js b/mod/forum/amd/src/grades/grader.js index 71c146d0650..5be32cf3f74 100644 --- a/mod/forum/amd/src/grades/grader.js +++ b/mod/forum/amd/src/grades/grader.js @@ -62,10 +62,11 @@ const getContentForUserIdFunction = (cmid, experimentalDisplayMode) => (userid) * * @param {Number} cmid * @param {Number} groupID + * @param {Boolean} onlyActive Whether to fetch only the active enrolled users or all enrolled users in the course. * @return {Array} Array of users for a given context. */ -const getUsersForCmidFunction = (cmid, groupID) => async() => { - const context = await CourseRepository.getUsersFromCourseModuleID(cmid, groupID); +const getUsersForCmidFunction = (cmid, groupID, onlyActive) => async() => { + const context = await CourseRepository.getUsersFromCourseModuleID(cmid, groupID, onlyActive); return context.users; }; @@ -128,9 +129,10 @@ const launchWholeForumGrading = async(rootNode, { ); const groupID = data.group ? data.group : 0; + const onlyActive = data.gradeOnlyActiveUsers; await Grader.launch( - getUsersForCmidFunction(data.cmid, groupID), + getUsersForCmidFunction(data.cmid, groupID, onlyActive), getContentForUserIdFunction(data.cmid, data.experimentalDisplayMode == "1"), gradingPanelFunctions.getter, gradingPanelFunctions.setter, diff --git a/mod/forum/classes/grades/forum_gradeitem.php b/mod/forum/classes/grades/forum_gradeitem.php index 0094e578cf3..5760cb61b0e 100644 --- a/mod/forum/classes/grades/forum_gradeitem.php +++ b/mod/forum/classes/grades/forum_gradeitem.php @@ -236,6 +236,23 @@ class forum_gradeitem extends component_gradeitem { return (int) $this->forum->get_id(); } + /** + * Defines whether only active users in the course should be gradeable. + * + * @return bool Whether only active users in the course should be gradeable. + */ + public function should_grade_only_active_users(): bool { + global $CFG; + + $showonlyactiveenrolconfig = !empty($CFG->grade_report_showonlyactiveenrol); + // Grade only active users enrolled in the course either when the 'grade_report_showonlyactiveenrol' user + // preference is set to true or the current user does not have the capability to view suspended users in the + // course. In cases where the 'grade_report_showonlyactiveenrol' user preference is not set we are falling back + // to the set value for the 'grade_report_showonlyactiveenrol' config. + return get_user_preferences('grade_report_showonlyactiveenrol', $showonlyactiveenrolconfig) || + !has_capability('moodle/course:viewsuspendedusers', \context_course::instance($this->forum->get_course_id())); + } + /** * Create or update the grade. * diff --git a/mod/forum/classes/local/renderers/discussion_list.php b/mod/forum/classes/local/renderers/discussion_list.php index 6c57ab736b1..d2664393faa 100644 --- a/mod/forum/classes/local/renderers/discussion_list.php +++ b/mod/forum/classes/local/renderers/discussion_list.php @@ -197,6 +197,7 @@ class discussion_list { 'gradingcomponent' => $this->forumgradeitem->get_grading_component_name(), 'gradingcomponentsubtype' => $this->forumgradeitem->get_grading_component_subtype(), 'sendstudentnotifications' => $forum->should_notify_students_default_when_grade_for_forum(), + 'gradeonlyactiveusers' => $this->forumgradeitem->should_grade_only_active_users(), 'hasanyactions' => $hasanyactions, 'groupchangemenu' => groups_print_activity_menu( $cm, diff --git a/mod/forum/templates/grades/grade_button.mustache b/mod/forum/templates/grades/grade_button.mustache index e84e9a0fb82..ba51b3587b3 100644 --- a/mod/forum/templates/grades/grade_button.mustache +++ b/mod/forum/templates/grades/grade_button.mustache @@ -43,6 +43,7 @@ data-grading-component="{{gradingcomponent}}" data-grading-component-subtype="{{gradingcomponentsubtype}}" data-gradable-itemtype="forum" + data-grade-only-active-users="{{gradeonlyactiveusers}}" {{#sendstudentnotifications}}data-send-student-notifications="1" {{/sendstudentnotifications}} > {{#str}}gradeusers, forum{{/str}} diff --git a/mod/forum/tests/grades_forum_gradeitem_test.php b/mod/forum/tests/grades_forum_gradeitem_test.php index a7da3930c25..2e820fa4f7b 100644 --- a/mod/forum/tests/grades_forum_gradeitem_test.php +++ b/mod/forum/tests/grades_forum_gradeitem_test.php @@ -169,6 +169,91 @@ class grades_forum_gradeitem_test extends \advanced_testcase { ]); } + /** + * Test the logic in the should_grade_only_active_users() method. + * + * @covers ::should_grade_only_active_users + * @dataProvider should_grade_only_active_users_provider + * @param bool $showonlyactiveenrolconfig Whether the 'grade_report_showonlyactiveenrol' is enabled or not. + * @param bool $showonlyactiveenrolpreference Whether the 'grade_report_showonlyactiveenrol' preference is enabled or not. + * @param bool $viewsuspendeduserscapability Whether the 'moodle/course:viewsuspendedusers' capability is allowed or not. + * @param bool $expected The expected result. + */ + public function test_should_grade_only_active_users(bool $showonlyactiveenrolconfig, bool $showonlyactiveenrolpreference, + bool $viewsuspendeduserscapability, bool $expected): void { + global $DB; + + $this->resetAfterTest(); + + $forum = $this->get_forum_instance([ + 'grade_forum' => 100, + ]); + $course = $forum->get_course_record(); + $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); + + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher'); + $editingteacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']); + + // Set the 'grade_report_showonlyactiveenrol' config setting. + set_config('grade_report_showonlyactiveenrol', $showonlyactiveenrolconfig); + + $this->setUser($user); + // Set the 'grade_report_showonlyactiveenrol' user preference. + set_user_preference('grade_report_showonlyactiveenrol', $showonlyactiveenrolpreference); + + // Set the 'moodle/course:viewsuspendedusers' user capability. + assign_capability('moodle/course:viewsuspendedusers', $viewsuspendeduserscapability ? + CAP_ALLOW : CAP_PROHIBIT, $editingteacherroleid, \context_course::instance($course->id)); + + $this->assertEquals($expected, $gradeitem->should_grade_only_active_users()); + } + + /** + * Data provider for test_should_grade_only_active_users. + * + * @return array + */ + public function should_grade_only_active_users_provider(): array { + return [ + 'Enabled showonlyactiveenrol setting; enabled showonlyactiveenrol preference; view suspended users capability' => + [ + 'showonlyactiveenrolconfig' => true, + 'showonlyactiveenrolpreference' => true, + 'viewsuspendeduserscapability' => true, + 'expected' => true, + ], + 'Disabled showonlyactiveenrol setting; enabled showonlyactiveenrol preference; view suspended users capability' => + [ + 'showonlyactiveenrolconfig' => false, + 'showonlyactiveenrolpreference' => true, + 'viewsuspendeduserscapability' => true, + 'expected' => true, + ], + 'Disabled showonlyactiveenrol setting; disabled showonlyactiveenrol preference; view suspended users capability' => + [ + 'showonlyactiveenrolconfig' => false, + 'showonlyactiveenrolpreference' => false, + 'viewsuspendeduserscapability' => true, + 'expected' => false, + ], + 'Disabled showonlyactiveenrol setting; disabled showonlyactiveenrol preference; no view suspended users capability' => + [ + 'showonlyactiveenrolconfig' => false, + 'showonlyactiveenrolpreference' => false, + 'viewsuspendeduserscapability' => false, + 'expected' => true, + ], + 'Enabled showonlyactiveenrol setting; enabled showonlyactiveenrol preference; no view suspended users capability' => + [ + 'showonlyactiveenrolconfig' => true, + 'showonlyactiveenrolpreference' => true, + 'viewsuspendeduserscapability' => false, + 'expected' => true, + ], + ]; + } + /** * Get a forum instance. * diff --git a/mod/forum/view.php b/mod/forum/view.php index fd7c14c132f..c45e8eba055 100644 --- a/mod/forum/view.php +++ b/mod/forum/view.php @@ -197,6 +197,7 @@ switch ($forum->get_type()) { 'gradingcomponent' => $forumgradeitem->get_grading_component_name(), 'gradingcomponentsubtype' => $forumgradeitem->get_grading_component_subtype(), 'sendstudentnotifications' => $forum->should_notify_students_default_when_grade_for_forum(), + 'gradeonlyactiveusers' => $forumgradeitem->should_grade_only_active_users(), ]; echo $OUTPUT->render_from_template('mod_forum/grades/grade_button', $gradeobj); }