From 13c201fbc72da08dc43df4c21cb2c26eb9c25606 Mon Sep 17 00:00:00 2001 From: Ilya Tregubov Date: Thu, 26 Oct 2023 12:59:26 +0800 Subject: [PATCH] MDL-68652 mod_forum: Only include gradable users in grading --- course/amd/build/repository.min.js | 2 +- course/amd/build/repository.min.js.map | 2 +- course/amd/src/repository.js | 22 ++++++++ grade/classes/external/get_gradable_users.php | 55 +++---------------- grade/lib.php | 1 + grade/report/lib.php | 3 +- .../external/get_gradable_users_test.php | 26 ++++----- grade/upgrade.txt | 5 +- 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 +-- .../templates/grades/grade_button.mustache | 2 +- .../local/grader/user_picker/user.mustache | 4 +- mod/forum/tests/behat/grade_forum.feature | 15 +++++ 14 files changed, 75 insertions(+), 74 deletions(-) diff --git a/course/amd/build/repository.min.js b/course/amd/build/repository.min.js index 090572e95e2..0427d07e0cb 100644 --- a/course/amd/build/repository.min.js +++ b/course/amd/build/repository.min.js @@ -5,6 +5,6 @@ define("core_course/repository",["exports","core/ajax"],(function(_exports,_ajax * @module core_course/repository * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};var _default={getEnrolledCoursesByTimelineClassification:(classification,limit,offset,sort)=>{const args={classification:classification};void 0!==limit&&(args.limit=limit),void 0!==offset&&(args.offset=offset),void 0!==sort&&(args.sort=sort);const request={methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:args};return _ajax.default.call([request])[0]},getLastAccessedCourses:(userid,limit,offset,sort)=>{const args={};void 0!==userid&&(args.userid=userid),void 0!==limit&&(args.limit=limit),void 0!==offset&&(args.offset=offset),void 0!==sort&&(args.sort=sort);const request={methodname:"core_course_get_recent_courses",args:args};return _ajax.default.call([request])[0]},getUsersFromCourseModuleID:function(cmid,groupID){let onlyActive=arguments.length>2&&void 0!==arguments[2]&&arguments[2];var request={methodname:"core_course_get_enrolled_users_by_cmid",args:{cmid:cmid,groupid:groupID,onlyactive:onlyActive}};return _ajax.default.call([request])[0]},toggleManualCompletion:(cmid,completed)=>{const request={methodname:"core_completion_update_activity_completion_status_manually",args:{cmid:cmid,completed:completed}};return _ajax.default.call([request])[0]},getEnrolledCoursesWithEventsByTimelineClassification:function(classification){let limit=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,offset=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,sort=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,searchValue=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,eventsFrom=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null,eventsTo=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null;const args={classification:classification,limit:limit,offset:offset,sort:sort,eventsfrom:eventsFrom,eventsto:eventsTo,searchvalue:searchValue},request={methodname:"core_course_get_enrolled_courses_with_action_events_by_timeline_classification",args:args};return _ajax.default.call([request])[0]}};return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};var _default={getEnrolledCoursesByTimelineClassification:(classification,limit,offset,sort)=>{const args={classification:classification};void 0!==limit&&(args.limit=limit),void 0!==offset&&(args.offset=offset),void 0!==sort&&(args.sort=sort);const request={methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:args};return _ajax.default.call([request])[0]},getLastAccessedCourses:(userid,limit,offset,sort)=>{const args={};void 0!==userid&&(args.userid=userid),void 0!==limit&&(args.limit=limit),void 0!==offset&&(args.offset=offset),void 0!==sort&&(args.sort=sort);const request={methodname:"core_course_get_recent_courses",args:args};return _ajax.default.call([request])[0]},getUsersFromCourseModuleID:function(cmid,groupID){let onlyActive=arguments.length>2&&void 0!==arguments[2]&&arguments[2];var request={methodname:"core_course_get_enrolled_users_by_cmid",args:{cmid:cmid,groupid:groupID,onlyactive:onlyActive}};return _ajax.default.call([request])[0]},getGradableUsersFromCourseID:function(courseid,groupID){let onlyActive=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const request={methodname:"core_grades_get_gradable_users",args:{courseid:courseid,groupid:groupID,onlyactive:onlyActive}};return _ajax.default.call([request])[0]},toggleManualCompletion:(cmid,completed)=>{const request={methodname:"core_completion_update_activity_completion_status_manually",args:{cmid:cmid,completed:completed}};return _ajax.default.call([request])[0]},getEnrolledCoursesWithEventsByTimelineClassification:function(classification){let limit=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,offset=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,sort=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,searchValue=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,eventsFrom=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null,eventsTo=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null;const args={classification:classification,limit:limit,offset:offset,sort:sort,eventsfrom:eventsFrom,eventsto:eventsTo,searchvalue:searchValue},request={methodname:"core_course_get_enrolled_courses_with_action_events_by_timeline_classification",args:args};return _ajax.default.call([request])[0]}};return _exports.default=_default,_exports.default})); //# sourceMappingURL=repository.min.js.map \ No newline at end of file diff --git a/course/amd/build/repository.min.js.map b/course/amd/build/repository.min.js.map index b2b7eeddad9..93501533ef5 100644 --- a/course/amd/build/repository.min.js.map +++ b/course/amd/build/repository.min.js.map @@ -1 +1 @@ -{"version":3,"file":"repository.min.js","sources":["../src/repository.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 * A javascript module to handle course ajax actions.\n *\n * @module core_course/repository\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Get the list of courses that the logged in user is enrolled in for a given\n * timeline classification.\n *\n * @param {string} classification past, inprogress, or future\n * @param {int} limit Only return this many results\n * @param {int} offset Skip this many results from the start of the result set\n * @param {string} sort Column to sort by and direction, e.g. 'shortname asc'\n * @return {object} jQuery promise resolved with courses.\n */\nconst getEnrolledCoursesByTimelineClassification = (classification, limit, offset, sort) => {\n const args = {\n classification: classification\n };\n\n if (typeof limit !== 'undefined') {\n args.limit = limit;\n }\n\n if (typeof offset !== 'undefined') {\n args.offset = offset;\n }\n\n if (typeof sort !== 'undefined') {\n args.sort = sort;\n }\n\n const request = {\n methodname: 'core_course_get_enrolled_courses_by_timeline_classification',\n args: args\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get a list of courses that the logged in user is enrolled in, where they have at least one action event,\n * for a given timeline classification.\n *\n * @param {string} classification past, inprogress, or future\n * @param {int} limit The maximum number of courses to return\n * @param {int} offset Skip this many results from the start of the result set\n * @param {string} sort Column to sort by and direction, e.g. 'shortname asc'\n * @param {string} searchValue Optional text search value\n * @param {int} eventsFrom Optional start timestamp (inclusive) that the course should have event(s) in\n * @param {int} eventsTo Optional end timestamp (inclusive) that the course should have event(s) in\n * @return {object} jQuery promise resolved with courses.\n */\n const getEnrolledCoursesWithEventsByTimelineClassification = (classification, limit = 0, offset = 0, sort = null,\n searchValue = null, eventsFrom = null, eventsTo = null) => {\n\n const args = {\n classification: classification,\n limit: limit,\n offset: offset,\n sort: sort,\n eventsfrom: eventsFrom,\n eventsto: eventsTo,\n searchvalue: searchValue,\n };\n\n const request = {\n methodname: 'core_course_get_enrolled_courses_with_action_events_by_timeline_classification',\n args: args\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get the list of courses that the user has most recently accessed.\n *\n * @method getLastAccessedCourses\n * @param {int} userid User from which the courses will be obtained\n * @param {int} limit Only return this many results\n * @param {int} offset Skip this many results from the start of the result set\n * @param {string} sort Column to sort by and direction, e.g. 'shortname asc'\n * @return {promise} Resolved with an array of courses\n */\nconst getLastAccessedCourses = (userid, limit, offset, sort) => {\n const args = {};\n\n if (typeof userid !== 'undefined') {\n args.userid = userid;\n }\n\n if (typeof limit !== 'undefined') {\n args.limit = limit;\n }\n\n if (typeof offset !== 'undefined') {\n args.offset = offset;\n }\n\n if (typeof sort !== 'undefined') {\n args.sort = sort;\n }\n\n const request = {\n methodname: 'core_course_get_recent_courses',\n args: args\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get the list of users enrolled in this cmid.\n *\n * @param {Number} cmid Course Module from which the users will be obtained\n * @param {Number} groupID Group ID from which the users will be obtained\n * @param {Boolean} onlyActive Whether to fetch only the active enrolled users or all enrolled users in the course.\n * @returns {Promise} Promise containing a list of users\n */\nconst getEnrolledUsersFromCourseModuleID = (cmid, groupID, onlyActive = false) => {\n var request = {\n methodname: 'core_course_get_enrolled_users_by_cmid',\n args: {\n cmid: cmid,\n groupid: groupID,\n onlyactive: onlyActive,\n },\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Toggle the completion state of an activity with manual completion.\n *\n * @param {Number} cmid The course module ID.\n * @param {Boolean} completed Whether to set as complete or not.\n * @returns {object} jQuery promise\n */\nconst toggleManualCompletion = (cmid, completed) => {\n const request = {\n methodname: 'core_completion_update_activity_completion_status_manually',\n args: {\n cmid,\n completed,\n }\n };\n return Ajax.call([request])[0];\n};\n\nexport default {\n getEnrolledCoursesByTimelineClassification,\n getLastAccessedCourses,\n getUsersFromCourseModuleID: getEnrolledUsersFromCourseModuleID,\n toggleManualCompletion,\n getEnrolledCoursesWithEventsByTimelineClassification,\n};\n"],"names":["getEnrolledCoursesByTimelineClassification","classification","limit","offset","sort","args","request","methodname","Ajax","call","getLastAccessedCourses","userid","getUsersFromCourseModuleID","cmid","groupID","onlyActive","groupid","onlyactive","toggleManualCompletion","completed","getEnrolledCoursesWithEventsByTimelineClassification","searchValue","eventsFrom","eventsTo","eventsfrom","eventsto","searchvalue"],"mappings":";;;;;;;sJA0Ke,CACXA,2CAxI+C,CAACC,eAAgBC,MAAOC,OAAQC,cACzEC,KAAO,CACTJ,eAAgBA,qBAGC,IAAVC,QACPG,KAAKH,MAAQA,YAGK,IAAXC,SACPE,KAAKF,OAASA,aAGE,IAATC,OACPC,KAAKD,KAAOA,YAGVE,QAAU,CACZC,WAAY,8DACZF,KAAMA,aAGHG,cAAKC,KAAK,CAACH,UAAU,IAmH5BI,uBApE2B,CAACC,OAAQT,MAAOC,OAAQC,cAC7CC,KAAO,QAES,IAAXM,SACPN,KAAKM,OAASA,aAGG,IAAVT,QACPG,KAAKH,MAAQA,YAGK,IAAXC,SACPE,KAAKF,OAASA,aAGE,IAATC,OACPC,KAAKD,KAAOA,YAGVE,QAAU,CACZC,WAAY,iCACZF,KAAMA,aAGHG,cAAKC,KAAK,CAACH,UAAU,IA6C5BM,2BAlCuC,SAACC,KAAMC,aAASC,uEACnDT,QAAU,CACVC,WAAY,yCACZF,KAAM,CACFQ,KAAMA,KACNG,QAASF,QACTG,WAAYF,oBAIbP,cAAKC,KAAK,CAACH,UAAU,IAyB5BY,uBAf2B,CAACL,KAAMM,mBAC5Bb,QAAU,CACZC,WAAY,6DACZF,KAAM,CACFQ,KAAAA,KACAM,UAAAA,mBAGDX,cAAKC,KAAK,CAACH,UAAU,IAQ5Bc,qDAtG0D,SAACnB,oBAAgBC,6DAAQ,EAAGC,8DAAS,EAAGC,4DAAO,KACrGiB,mEAAc,KAAMC,kEAAa,KAAMC,gEAAW,WAEhDlB,KAAO,CACTJ,eAAgBA,eAChBC,MAAOA,MACPC,OAAQA,OACRC,KAAMA,KACNoB,WAAYF,WACZG,SAAUF,SACVG,YAAaL,aAGXf,QAAU,CACZC,WAAY,iFACZF,KAAMA,aAGHG,cAAKC,KAAK,CAACH,UAAU"} \ No newline at end of file +{"version":3,"file":"repository.min.js","sources":["../src/repository.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 * A javascript module to handle course ajax actions.\n *\n * @module core_course/repository\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Get the list of courses that the logged in user is enrolled in for a given\n * timeline classification.\n *\n * @param {string} classification past, inprogress, or future\n * @param {int} limit Only return this many results\n * @param {int} offset Skip this many results from the start of the result set\n * @param {string} sort Column to sort by and direction, e.g. 'shortname asc'\n * @return {object} jQuery promise resolved with courses.\n */\nconst getEnrolledCoursesByTimelineClassification = (classification, limit, offset, sort) => {\n const args = {\n classification: classification\n };\n\n if (typeof limit !== 'undefined') {\n args.limit = limit;\n }\n\n if (typeof offset !== 'undefined') {\n args.offset = offset;\n }\n\n if (typeof sort !== 'undefined') {\n args.sort = sort;\n }\n\n const request = {\n methodname: 'core_course_get_enrolled_courses_by_timeline_classification',\n args: args\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get a list of courses that the logged in user is enrolled in, where they have at least one action event,\n * for a given timeline classification.\n *\n * @param {string} classification past, inprogress, or future\n * @param {int} limit The maximum number of courses to return\n * @param {int} offset Skip this many results from the start of the result set\n * @param {string} sort Column to sort by and direction, e.g. 'shortname asc'\n * @param {string} searchValue Optional text search value\n * @param {int} eventsFrom Optional start timestamp (inclusive) that the course should have event(s) in\n * @param {int} eventsTo Optional end timestamp (inclusive) that the course should have event(s) in\n * @return {object} jQuery promise resolved with courses.\n */\n const getEnrolledCoursesWithEventsByTimelineClassification = (classification, limit = 0, offset = 0, sort = null,\n searchValue = null, eventsFrom = null, eventsTo = null) => {\n\n const args = {\n classification: classification,\n limit: limit,\n offset: offset,\n sort: sort,\n eventsfrom: eventsFrom,\n eventsto: eventsTo,\n searchvalue: searchValue,\n };\n\n const request = {\n methodname: 'core_course_get_enrolled_courses_with_action_events_by_timeline_classification',\n args: args\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get the list of courses that the user has most recently accessed.\n *\n * @method getLastAccessedCourses\n * @param {int} userid User from which the courses will be obtained\n * @param {int} limit Only return this many results\n * @param {int} offset Skip this many results from the start of the result set\n * @param {string} sort Column to sort by and direction, e.g. 'shortname asc'\n * @return {promise} Resolved with an array of courses\n */\nconst getLastAccessedCourses = (userid, limit, offset, sort) => {\n const args = {};\n\n if (typeof userid !== 'undefined') {\n args.userid = userid;\n }\n\n if (typeof limit !== 'undefined') {\n args.limit = limit;\n }\n\n if (typeof offset !== 'undefined') {\n args.offset = offset;\n }\n\n if (typeof sort !== 'undefined') {\n args.sort = sort;\n }\n\n const request = {\n methodname: 'core_course_get_recent_courses',\n args: args\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get the list of users enrolled in this cmid.\n *\n * @param {Number} cmid Course Module from which the users will be obtained\n * @param {Number} groupID Group ID from which the users will be obtained\n * @param {Boolean} onlyActive Whether to fetch only the active enrolled users or all enrolled users in the course.\n * @returns {Promise} Promise containing a list of users\n */\nconst getEnrolledUsersFromCourseModuleID = (cmid, groupID, onlyActive = false) => {\n var request = {\n methodname: 'core_course_get_enrolled_users_by_cmid',\n args: {\n cmid: cmid,\n groupid: groupID,\n onlyactive: onlyActive,\n },\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get the list of gradable users enrolled in this course.\n *\n * @param {Number} courseid Course ID from which the users will be obtained\n * @param {Number} groupID Group ID from which the users will be obtained\n * @param {Boolean} onlyActive Whether to fetch only the active enrolled users or all enrolled users in the course.\n * @returns {Promise} Promise containing a list of users\n */\nconst getGradabaleUsersFromCourseID = (courseid, groupID, onlyActive = false) => {\n const request = {\n methodname: 'core_grades_get_gradable_users',\n args: {\n courseid: courseid,\n groupid: groupID,\n onlyactive: onlyActive,\n },\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Toggle the completion state of an activity with manual completion.\n *\n * @param {Number} cmid The course module ID.\n * @param {Boolean} completed Whether to set as complete or not.\n * @returns {object} jQuery promise\n */\nconst toggleManualCompletion = (cmid, completed) => {\n const request = {\n methodname: 'core_completion_update_activity_completion_status_manually',\n args: {\n cmid,\n completed,\n }\n };\n return Ajax.call([request])[0];\n};\n\nexport default {\n getEnrolledCoursesByTimelineClassification,\n getLastAccessedCourses,\n getUsersFromCourseModuleID: getEnrolledUsersFromCourseModuleID,\n getGradableUsersFromCourseID: getGradabaleUsersFromCourseID,\n toggleManualCompletion,\n getEnrolledCoursesWithEventsByTimelineClassification,\n};\n"],"names":["getEnrolledCoursesByTimelineClassification","classification","limit","offset","sort","args","request","methodname","Ajax","call","getLastAccessedCourses","userid","getUsersFromCourseModuleID","cmid","groupID","onlyActive","groupid","onlyactive","getGradableUsersFromCourseID","courseid","toggleManualCompletion","completed","getEnrolledCoursesWithEventsByTimelineClassification","searchValue","eventsFrom","eventsTo","eventsfrom","eventsto","searchvalue"],"mappings":";;;;;;;sJA+Le,CACXA,2CA7J+C,CAACC,eAAgBC,MAAOC,OAAQC,cACzEC,KAAO,CACTJ,eAAgBA,qBAGC,IAAVC,QACPG,KAAKH,MAAQA,YAGK,IAAXC,SACPE,KAAKF,OAASA,aAGE,IAATC,OACPC,KAAKD,KAAOA,YAGVE,QAAU,CACZC,WAAY,8DACZF,KAAMA,aAGHG,cAAKC,KAAK,CAACH,UAAU,IAwI5BI,uBAzF2B,CAACC,OAAQT,MAAOC,OAAQC,cAC7CC,KAAO,QAES,IAAXM,SACPN,KAAKM,OAASA,aAGG,IAAVT,QACPG,KAAKH,MAAQA,YAGK,IAAXC,SACPE,KAAKF,OAASA,aAGE,IAATC,OACPC,KAAKD,KAAOA,YAGVE,QAAU,CACZC,WAAY,iCACZF,KAAMA,aAGHG,cAAKC,KAAK,CAACH,UAAU,IAkE5BM,2BAvDuC,SAACC,KAAMC,aAASC,uEACnDT,QAAU,CACVC,WAAY,yCACZF,KAAM,CACFQ,KAAMA,KACNG,QAASF,QACTG,WAAYF,oBAIbP,cAAKC,KAAK,CAACH,UAAU,IA8C5BY,6BAnCkC,SAACC,SAAUL,aAASC,yEAChDT,QAAU,CACZC,WAAY,iCACZF,KAAM,CACFc,SAAUA,SACVH,QAASF,QACTG,WAAYF,oBAIbP,cAAKC,KAAK,CAACH,UAAU,IA0B5Bc,uBAhB2B,CAACP,KAAMQ,mBAC5Bf,QAAU,CACZC,WAAY,6DACZF,KAAM,CACFQ,KAAAA,KACAQ,UAAAA,mBAGDb,cAAKC,KAAK,CAACH,UAAU,IAS5BgB,qDA5H0D,SAACrB,oBAAgBC,6DAAQ,EAAGC,8DAAS,EAAGC,4DAAO,KACrGmB,mEAAc,KAAMC,kEAAa,KAAMC,gEAAW,WAEhDpB,KAAO,CACTJ,eAAgBA,eAChBC,MAAOA,MACPC,OAAQA,OACRC,KAAMA,KACNsB,WAAYF,WACZG,SAAUF,SACVG,YAAaL,aAGXjB,QAAU,CACZC,WAAY,iFACZF,KAAMA,aAGHG,cAAKC,KAAK,CAACH,UAAU"} \ No newline at end of file diff --git a/course/amd/src/repository.js b/course/amd/src/repository.js index 96dfa488f91..a48aa7a2714 100644 --- a/course/amd/src/repository.js +++ b/course/amd/src/repository.js @@ -150,6 +150,27 @@ const getEnrolledUsersFromCourseModuleID = (cmid, groupID, onlyActive = false) = return Ajax.call([request])[0]; }; +/** + * Get the list of gradable users enrolled in this course. + * + * @param {Number} courseid Course ID from which the users will be obtained + * @param {Number} groupID Group ID from which the users will be obtained + * @param {Boolean} onlyActive Whether to fetch only the active enrolled users or all enrolled users in the course. + * @returns {Promise} Promise containing a list of users + */ +const getGradabaleUsersFromCourseID = (courseid, groupID, onlyActive = false) => { + const request = { + methodname: 'core_grades_get_gradable_users', + args: { + courseid: courseid, + groupid: groupID, + onlyactive: onlyActive, + }, + }; + + return Ajax.call([request])[0]; +}; + /** * Toggle the completion state of an activity with manual completion. * @@ -172,6 +193,7 @@ export default { getEnrolledCoursesByTimelineClassification, getLastAccessedCourses, getUsersFromCourseModuleID: getEnrolledUsersFromCourseModuleID, + getGradableUsersFromCourseID: getGradabaleUsersFromCourseID, toggleManualCompletion, getEnrolledCoursesWithEventsByTimelineClassification, }; diff --git a/grade/classes/external/get_gradable_users.php b/grade/classes/external/get_gradable_users.php index 15edc2c3cc5..152ee87c4f7 100644 --- a/grade/classes/external/get_gradable_users.php +++ b/grade/classes/external/get_gradable_users.php @@ -22,9 +22,8 @@ require_once($CFG->dirroot . '/grade/lib.php'); require_once($CFG->dirroot . '/user/externallib.php'); use coding_exception; +use core_user_external; use external_api; -use core_user; -use external_description; use external_function_parameters; use external_multiple_structure; use external_single_structure; @@ -41,7 +40,6 @@ use user_picture; * @package core_grades * @copyright 2023 Ilya Tregubov * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 4.1 */ class get_gradable_users extends external_api { @@ -63,9 +61,9 @@ class get_gradable_users extends external_api { /** * Given a course ID find the gradable users within a group. * - * @param int $courseid - * @param int|null $groupid - * @param bool $onlyactive + * @param int $courseid Course ID + * @param int|null $groupid Group ID + * @param bool $onlyactive Whether we should only return active enrolments. * @return array Users and warnings. * @throws coding_exception * @throws invalid_parameter_exception @@ -91,25 +89,15 @@ class get_gradable_users extends external_api { require_capability('moodle/course:viewparticipants', $coursecontext); $course = $DB->get_record('course', ['id' => $params['courseid']]); - // Create a graded_users_iterator because it will properly check the groups etc. $onlyactive = $onlyactive || !has_capability('moodle/course:viewsuspendedusers', $coursecontext); - $gui = new \graded_users_iterator($course, null, $params['groupid']); - $gui->require_active_enrolment($onlyactive); - $gui->init(); - - // Flatten the users. - $users = []; - while ($user = $gui->next_user()) { - $users[$user->user->id] = $user->user; - } - $gui->close(); - + $users = get_gradable_users($course->id, $params['groupid'], $onlyactive); $users = array_map(function ($user) use ($PAGE) { $user->fullname = fullname($user); $userpicture = new user_picture($user); $userpicture->size = 1; - $user->profileimage = $userpicture->get_url($PAGE)->out(false); + $user->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false); + $user->profileimageurl = $userpicture->get_url($PAGE)->out(false); return $user; }, $users); sort($users); @@ -127,35 +115,8 @@ class get_gradable_users extends external_api { */ public static function execute_returns(): external_single_structure { return new external_single_structure([ - 'users' => new external_multiple_structure(self::user_description()), + 'users' => new external_multiple_structure(core_user_external::user_description()), 'warnings' => new external_warnings(), ]); } - - /** - * Create user return value description. - * - * @return external_description - */ - public static function user_description(): external_description { - $userfields = [ - 'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'), - 'profileimage' => new external_value( - PARAM_URL, - 'The location of the users larger image', - VALUE_OPTIONAL - ), - 'fullname' => new external_value(PARAM_TEXT, 'The full name of the user', VALUE_OPTIONAL), - 'firstname' => new external_value( - core_user::get_property_type('firstname'), - 'The first name(s) of the user', - VALUE_OPTIONAL), - 'lastname' => new external_value( - core_user::get_property_type('lastname'), - 'The family name of the user', - VALUE_OPTIONAL), - ]; - return new external_single_structure($userfields); - } - } diff --git a/grade/lib.php b/grade/lib.php index 181fbb26d06..3c7fefa2c9d 100644 --- a/grade/lib.php +++ b/grade/lib.php @@ -791,6 +791,7 @@ function grade_get_plugin_info($courseid, $active_type, $active_plugin) { */ function get_gradable_users(int $courseid, ?int $groupid = null, bool $onlyactiveenrol = false): array { $course = get_course($courseid); + // Create a graded_users_iterator because it will properly check the groups etc. $gui = new graded_users_iterator($course, null, $groupid); $gui->require_active_enrolment($onlyactiveenrol); $gui->init(); diff --git a/grade/report/lib.php b/grade/report/lib.php index 7989566ae7c..7b35f913cb5 100644 --- a/grade/report/lib.php +++ b/grade/report/lib.php @@ -761,14 +761,13 @@ abstract class grade_report { * * @param int $courseid The course ID. * @param int|null $groupid The group ID (optional). - * @return array $users A list of enrolled gradable users. + * @return array A list of enrolled gradable users. */ public static function get_gradable_users(int $courseid, ?int $groupid = null): array { global $CFG; require_once($CFG->dirroot . '/grade/lib.php'); $context = context_course::instance($courseid); - // Create a graded_users_iterator because it will properly check the groups etc. $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); $onlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol) || !has_capability('moodle/course:viewsuspendedusers', $context); diff --git a/grade/tests/external/get_gradable_users_test.php b/grade/tests/external/get_gradable_users_test.php index 028d9a72b59..456a1db2192 100644 --- a/grade/tests/external/get_gradable_users_test.php +++ b/grade/tests/external/get_gradable_users_test.php @@ -89,7 +89,7 @@ class get_gradable_users_test extends \externallib_advanced_testcase { 'fullname' => $user['fullname'], 'firstname' => $user['firstname'], 'lastname' => $user['lastname'], - 'profileimage' => $user['profileimage'], + 'profileimageurl' => $user['profileimageurl'], ]; }, array_values($result['users'])); $this->assertEquals($expected, $mapped); @@ -107,31 +107,31 @@ class get_gradable_users_test extends \externallib_advanced_testcase { false, [ [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Apple', 'lastname' => 'Apricot', 'fullname' => 'Apple Apricot', ], [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Banana', 'lastname' => 'Blueberry', 'fullname' => 'Banana Blueberry', ], [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Cherry', 'lastname' => 'Cranberry', 'fullname' => 'Cherry Cranberry', ], [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Durian', 'lastname' => 'Dracontomelon', 'fullname' => 'Durian Dracontomelon', ], [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Eggplant', 'lastname' => 'Ensete', 'fullname' => 'Eggplant Ensete', @@ -143,25 +143,25 @@ class get_gradable_users_test extends \externallib_advanced_testcase { false, [ [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Apple', 'lastname' => 'Apricot', 'fullname' => 'Apple Apricot', ], [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Banana', 'lastname' => 'Blueberry', 'fullname' => 'Banana Blueberry', ], [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Cherry', 'lastname' => 'Cranberry', 'fullname' => 'Cherry Cranberry', ], [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Durian', 'lastname' => 'Dracontomelon', 'fullname' => 'Durian Dracontomelon', @@ -173,19 +173,19 @@ class get_gradable_users_test extends \externallib_advanced_testcase { true, [ [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Apple', 'lastname' => 'Apricot', 'fullname' => 'Apple Apricot', ], [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Banana', 'lastname' => 'Blueberry', 'fullname' => 'Banana Blueberry', ], [ - 'profileimage' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', + 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1', 'firstname' => 'Cherry', 'lastname' => 'Cranberry', 'fullname' => 'Cherry Cranberry', diff --git a/grade/upgrade.txt b/grade/upgrade.txt index e1bc770df03..54622a2a7b1 100644 --- a/grade/upgrade.txt +++ b/grade/upgrade.txt @@ -1,8 +1,11 @@ This file describes API changes in /grade/* ; Information provided here is intended especially for developers. -=== 4.1.6 === +=== 4.1.7 === * The function get_gradable_users() in grade/lib.php has extra param now to retrieve only active enrolments. +* A new webservice core_grades_get_gradable_users has been added to retrieve gradable users for a course. + +=== 4.1.6 === * The grade `itemname` property contained in the return structure of the following external methods is now PARAM_CLEANHTML: - `gradereport_user_get_grade_items` diff --git a/mod/forum/amd/build/grades/grader.min.js b/mod/forum/amd/build/grades/grader.min.js index 2e0c3cf6510..81b69ac05ab 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,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)}}}))}})); + */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),getGradableUsersForCourseidFunction=(courseID,groupID,onlyActive)=>async()=>(await _repository2.default.getGradableUsersFromCourseID(courseID,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(getGradableUsersForCourseidFunction(data.courseId,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 b2e5cda5e3a..51fb5b2db6b 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 * @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 +{"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} courseID\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 getGradableUsersForCourseidFunction = (courseID, groupID, onlyActive) => async() => {\n const context = await CourseRepository.getGradableUsersFromCourseID(courseID, 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 getGradableUsersForCourseidFunction(data.courseId, 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","getGradableUsersForCourseidFunction","courseID","groupID","onlyActive","async","CourseRepository","getGradableUsersFromCourseID","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","courseId","getter","setter","groupid","initialUserId","initialuserid","moduleName","name","courseName","courseUrl","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,oCAAsC,CAACC,SAAUC,QAASC,aAAeC,gBACrDC,qBAAiBC,6BAA6BL,SAAUC,QAASC,aAExEI,MAIbC,iBAAmBC,MAAQA,KAAKC,QAAQC,UAAUC,cAQlDnB,qBAAwBoB,mBAEpBC,UAAY,IAAIC,IACtBF,WAAWG,MAAMC,YAAYC,SAAQC,MAAQL,UAAUM,IAAID,KAAKE,GAAIF,cAC9DG,UAAYT,WAAWG,MAAMO,UAAU/B,KAAI2B,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,OACTtC,oCAAoC6C,KAAKW,SAAUtD,QAASC,YAC5DpB,4BAA4B8D,KAAK7D,KAAsC,KAAhC6D,KAAK5D,yBAC5C8D,sBAAsBU,OACtBV,sBAAsBW,OACtB,CACIC,QAASd,KAAKc,QACdC,cAAef,KAAKgB,cACpBC,WAAYjB,KAAKkB,KACjBC,WAAYnB,KAAKmB,WACjBC,WAAW,oBAAY,mBAAoB,CAAC5C,GAAIwB,KAAKW,WACrDU,yBAA0BrB,KAAKqB,yBAC/BtB,aAAAA,eAmDcuB,CAAwB5B,SAAU,CACpCK,aAAcT,EAAEC,SAEtB,MAAOgC,6BACQrE,UAAUqE,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,sBAAsBU,OACtBZ,KAAK3D,OACL2D,KAAKkB,KACL,CACInB,aAAAA,eA6Cc2B,CAAkBhC,SAAU,CAC9BK,aAAcT,EAAEC,SAEtB,MAAOgC,6BACQrE,UAAUqE"} \ 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 5be32cf3f74..b1b4decdd42 100644 --- a/mod/forum/amd/src/grades/grader.js +++ b/mod/forum/amd/src/grades/grader.js @@ -60,13 +60,13 @@ const getContentForUserIdFunction = (cmid, experimentalDisplayMode) => (userid) * Curried function with CMID set, this is then used in unified grader as a fetch users call. * The function curried fetches all users in a course for a given CMID. * - * @param {Number} cmid + * @param {Number} courseID * @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, onlyActive) => async() => { - const context = await CourseRepository.getUsersFromCourseModuleID(cmid, groupID, onlyActive); +const getGradableUsersForCourseidFunction = (courseID, groupID, onlyActive) => async() => { + const context = await CourseRepository.getGradableUsersFromCourseID(courseID, groupID, onlyActive); return context.users; }; @@ -132,7 +132,7 @@ const launchWholeForumGrading = async(rootNode, { const onlyActive = data.gradeOnlyActiveUsers; await Grader.launch( - getUsersForCmidFunction(data.cmid, groupID, onlyActive), + getGradableUsersForCourseidFunction(data.courseId, groupID, onlyActive), getContentForUserIdFunction(data.cmid, data.experimentalDisplayMode == "1"), gradingPanelFunctions.getter, gradingPanelFunctions.setter, diff --git a/mod/forum/templates/grades/grade_button.mustache b/mod/forum/templates/grades/grade_button.mustache index ba51b3587b3..3dfcd4a7674 100644 --- a/mod/forum/templates/grades/grade_button.mustache +++ b/mod/forum/templates/grades/grade_button.mustache @@ -52,4 +52,4 @@ require(['mod_forum/grades/grader'], function(Grader) { Grader.registerLaunchListeners(); }); -{{/js}} \ No newline at end of file +{{/js}} diff --git a/mod/forum/templates/local/grades/local/grader/user_picker/user.mustache b/mod/forum/templates/local/grades/local/grader/user_picker/user.mustache index 488f68d6575..26a0b97d29c 100644 --- a/mod/forum/templates/local/grades/local/grader/user_picker/user.mustache +++ b/mod/forum/templates/local/grades/local/grader/user_picker/user.mustache @@ -38,13 +38,13 @@ "total": 7 } }} -{{#profileimage}} +{{#profileimageurl}} -{{/profileimage}} +{{/profileimageurl}}
Type" to "Point" + And I set the field "Whole forum grading > Maximum grade" to "10" + And I set the field "Whole forum grading > Grade to pass" to "4" + And I press "Save and display" + And I press "Grade users" + Then I should see "1 out of 1" + And I should see "Student 1" + And I should not see "Teacher 1" + And I press "Save changes and proceed to the next user" + And I should see "Student 1" + And I should not see "Teacher 1"