first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
/**
* Module for viewing a discussion.
*
* @module mod_forum/discussion
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/discussion",["jquery","core/custom_interaction_events","mod_forum/selectors","core/pubsub","mod_forum/forum_events","core/str","core/notification"],(function($,CustomEvents,Selectors,PubSub,ForumEvents,String,Notification){var isElementInInPageReplySection=function(element){return!!$(element).closest(Selectors.post.inpageReplyContent).length},initAccessibilityKeyboardNav=function(root){root.find(Selectors.post.post).each((function(index,post){var actions=$(post).find(Selectors.post.action),firstAction=actions.first();actions.attr("tabindex","-1"),firstAction.attr("tabindex",0)})),CustomEvents.define(root,[CustomEvents.events.up,CustomEvents.events.down,CustomEvents.events.next,CustomEvents.events.previous,CustomEvents.events.home,CustomEvents.events.end]),root.on(CustomEvents.events.up,(function(e,data){var activeElement=document.activeElement;if(!isElementInInPageReplySection(activeElement)){var focusPost=$(activeElement).closest(Selectors.post.post);focusPost.length?function(currentPost){var prevPost=currentPost.prev(Selectors.post.post);if(prevPost.length){var replyPost=prevPost.find(Selectors.post.post).last();replyPost.length?replyPost.focus():prevPost.focus()}else currentPost.parents(Selectors.post.post).first().focus()}(focusPost):root.find(Selectors.post.post).first().focus(),data.originalEvent.preventDefault()}})),root.on(CustomEvents.events.down,(function(e,data){var activeElement=document.activeElement;if(!isElementInInPageReplySection(activeElement)){var focusPost=$(activeElement).closest(Selectors.post.post);focusPost.length?function(currentPost){var replyPost=currentPost.find(Selectors.post.post).first();if(replyPost.length)replyPost.focus();else{var siblingPost=currentPost.next(Selectors.post.post);if(siblingPost.length)siblingPost.focus();else for(var parentPosts=currentPost.parents(Selectors.post.post).toArray(),i=0;i<parentPosts.length;i++){var ancestorSiblingPost=$(parentPosts[i]).next(Selectors.post.post);if(ancestorSiblingPost.length){ancestorSiblingPost.focus();break}}}}(focusPost):root.find(Selectors.post.post).first().focus(),data.originalEvent.preventDefault()}})),root.on(CustomEvents.events.home,(function(e,data){isElementInInPageReplySection(document.activeElement)||(root.find(Selectors.post.post).first().focus(),data.originalEvent.preventDefault())})),root.on(CustomEvents.events.end,(function(e,data){isElementInInPageReplySection(document.activeElement)||(root.find(Selectors.post.post).last().focus(),data.originalEvent.preventDefault())})),root.on(CustomEvents.events.next,Selectors.post.action,(function(e,data){var currentAction=$(e.target),actions=currentAction.closest(Selectors.post.actionsContainer).find(Selectors.post.action),nextAction=currentAction.next(Selectors.post.action);actions.attr("tabindex","-1"),nextAction.length||(nextAction=actions.first()),nextAction.attr("tabindex",0),nextAction.focus(),data.originalEvent.preventDefault()})),root.on(CustomEvents.events.previous,Selectors.post.action,(function(e,data){var currentAction=$(e.target),actions=currentAction.closest(Selectors.post.actionsContainer).find(Selectors.post.action),nextAction=currentAction.prev(Selectors.post.action);actions.attr("tabindex","-1"),nextAction.length||(nextAction=actions.last()),nextAction.attr("tabindex",0),nextAction.focus(),data.originalEvent.preventDefault()})),root.on(CustomEvents.events.home,Selectors.post.action,(function(e,data){var actions=$(e.target).closest(Selectors.post.actionsContainer).find(Selectors.post.action),firstAction=actions.first();actions.attr("tabindex","-1"),firstAction.attr("tabindex",0),firstAction.focus(),e.stopPropagation(),data.originalEvent.preventDefault()})),root.on(CustomEvents.events.end,Selectors.post.action,(function(e,data){var actions=$(e.target).closest(Selectors.post.actionsContainer).find(Selectors.post.action),lastAction=actions.last();actions.attr("tabindex","-1"),lastAction.attr("tabindex",0),lastAction.focus(),e.stopPropagation(),data.originalEvent.preventDefault()})),PubSub.subscribe(ForumEvents.SUBSCRIPTION_TOGGLED,(function(data){var updateMessage=data.subscriptionState?"discussionsubscribed":"discussionunsubscribed";String.get_string(updateMessage,"forum").then((function(s){return Notification.addNotification({message:s,type:"info"})})).catch(Notification.exception)}))};return{init:function(root){initAccessibilityKeyboardNav(root)}}}));
//# sourceMappingURL=discussion.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* Module for the list of discussions on when viewing a forum.
*
* @module mod_forum/discussion_list
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/discussion_list",["jquery","core/templates","core/str","core/notification","mod_forum/subscription_toggle","mod_forum/selectors","mod_forum/repository","core/pubsub","mod_forum/forum_events","core_form/changechecker"],(function($,Templates,Str,Notification,SubscriptionToggle,Selectors,Repository,PubSub,ForumEvents,FormChangeChecker){return{init:function(root){SubscriptionToggle.init(root,!1,(function(toggleElement,context){var toggleId=toggleElement.attr("id"),newTargetState=context.userstate.subscribed?0:1;toggleElement.data("targetstate",newTargetState);var stringKey=context.userstate.subscribed?"unsubscribediscussion":"subscribediscussion";return Str.get_string(stringKey,"mod_forum").then((function(string){return toggleElement.closest("td").find('label[for="'+toggleId+'"]').find("span").text(string),string}))})),function(root){PubSub.subscribe(ForumEvents.SUBSCRIPTION_TOGGLED,(function(data){var discussionId=data.discussionId,subscribed=data.subscriptionState,discussionListItem=root.find(Selectors.discussion.item+"[data-discussionid= "+discussionId+"]"),subscribedLabel=discussionListItem.find(Selectors.discussion.subscribedLabel);subscribed?(discussionListItem.addClass("subscribed"),subscribedLabel.removeAttr("hidden")):(discussionListItem.removeClass("subscribed"),subscribedLabel.attr("hidden",!0))})),root.on("click",Selectors.post.inpageCancelButton,(function(e){FormChangeChecker.resetFormDirtyState(e.currentTarget)})),root.on("click",Selectors.favourite.toggle,(function(e){e.preventDefault();var toggleElement=$(this),forumId=toggleElement.data("forumid"),discussionId=toggleElement.data("discussionid"),subscriptionState=toggleElement.data("targetstate");Repository.setFavouriteDiscussionState(forumId,discussionId,subscriptionState).then((function(){return location.reload()})).catch(Notification.exception)})),root.on("click",Selectors.pin.toggle,(function(e){e.preventDefault();var toggleElement=$(this),forumId=toggleElement.data("forumid"),discussionId=toggleElement.data("discussionid"),state=toggleElement.data("targetstate");Repository.setPinDiscussionState(forumId,discussionId,state).then((function(){return location.reload()})).catch(Notification.exception)})),root.on("click",Selectors.lock.toggle,(function(e){var toggleElement=$(this),forumId=toggleElement.data("forumid"),discussionId=toggleElement.data("discussionid"),state=toggleElement.data("state");Repository.setDiscussionLockState(forumId,discussionId,state).then((function(context){var icon=toggleElement.parents(Selectors.summary.actions).find(Selectors.lock.icon),lockedLabel=toggleElement.parents(Selectors.discussion.item).find(Selectors.discussion.lockedLabel);return context.locked?(icon.removeClass("hidden"),lockedLabel.removeAttr("hidden")):(icon.addClass("hidden"),lockedLabel.attr("hidden",!0)),context})).then((function(context){return context.forumid=forumId,Templates.render("mod_forum/discussion_lock_toggle",context)})).then((function(html,js){return Templates.replaceNode(toggleElement,html,js)})).then((function(){return Str.get_string("lockupdated","forum").done((function(s){return Notification.addNotification({message:s,type:"info"})}))})).catch(Notification.exception),e.preventDefault()}))}(root)}}}));
//# sourceMappingURL=discussion_list.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Handle discussion subscription toggling on a discussion list in
* the forum view.
*
* @module mod_forum/favourite_toggle
* @copyright 2019 Peter Dias <peter@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/favourite_toggle",["jquery","core/templates","core/notification","mod_forum/repository","mod_forum/selectors","core/str"],(function($,Templates,Notification,Repository,Selectors,String){return{init:function(root,preventDefault,callback){root.on("click",Selectors.favourite.toggle,(function(e){var toggleElement=$(this),forumId=toggleElement.data("forumid"),discussionId=toggleElement.data("discussionid"),subscriptionState=toggleElement.data("targetstate");Repository.setFavouriteDiscussionState(forumId,discussionId,subscriptionState).then((function(context){return callback(toggleElement,context)})).then((function(){return String.get_string("favouriteupdated","forum").done((function(s){return Notification.addNotification({message:s,type:"info"})}))})).catch(Notification.exception),preventDefault&&e.preventDefault()}))}}}));
//# sourceMappingURL=favourite_toggle.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"favourite_toggle.min.js","sources":["../src/favourite_toggle.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Handle discussion subscription toggling on a discussion list in\n * the forum view.\n *\n * @module mod_forum/favourite_toggle\n * @copyright 2019 Peter Dias <peter@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/templates',\n 'core/notification',\n 'mod_forum/repository',\n 'mod_forum/selectors',\n 'core/str',\n ], function(\n $,\n Templates,\n Notification,\n Repository,\n Selectors,\n String\n ) {\n\n /**\n * Register event listeners for the subscription toggle.\n *\n * @param {object} root The discussion list root element\n * @param {boolean} preventDefault Should the default action of the event be prevented\n * @param {function} callback Success callback\n */\n var registerEventListeners = function(root, preventDefault, callback) {\n root.on('click', Selectors.favourite.toggle, function(e) {\n var toggleElement = $(this);\n var forumId = toggleElement.data('forumid');\n var discussionId = toggleElement.data('discussionid');\n var subscriptionState = toggleElement.data('targetstate');\n\n Repository.setFavouriteDiscussionState(forumId, discussionId, subscriptionState)\n .then(function(context) {\n return callback(toggleElement, context);\n })\n .then(function() {\n return String.get_string(\"favouriteupdated\", \"forum\")\n .done(function(s) {\n return Notification.addNotification({\n message: s,\n type: \"info\"\n });\n });\n })\n .catch(Notification.exception);\n\n if (preventDefault) {\n e.preventDefault();\n }\n });\n };\n\n return {\n init: registerEventListeners\n };\n});\n"],"names":["define","$","Templates","Notification","Repository","Selectors","String","init","root","preventDefault","callback","on","favourite","toggle","e","toggleElement","this","forumId","data","discussionId","subscriptionState","setFavouriteDiscussionState","then","context","get_string","done","s","addNotification","message","type","catch","exception"],"mappings":";;;;;;;;AAuBAA,oCAAO,CACC,SACA,iBACA,oBACA,uBACA,sBACA,aACD,SACCC,EACAC,UACAC,aACAC,WACAC,UACAC,cAsCG,CACHC,KA7ByB,SAASC,KAAMC,eAAgBC,UACxDF,KAAKG,GAAG,QAASN,UAAUO,UAAUC,QAAQ,SAASC,OAC9CC,cAAgBd,EAAEe,MAClBC,QAAUF,cAAcG,KAAK,WAC7BC,aAAeJ,cAAcG,KAAK,gBAClCE,kBAAoBL,cAAcG,KAAK,eAE3Cd,WAAWiB,4BAA4BJ,QAASE,aAAcC,mBACzDE,MAAK,SAASC,gBACJb,SAASK,cAAeQ,YAElCD,MAAK,kBACKhB,OAAOkB,WAAW,mBAAoB,SACxCC,MAAK,SAASC,UACJvB,aAAawB,gBAAgB,CAChCC,QAASF,EACTG,KAAM,eAIrBC,MAAM3B,aAAa4B,WAEpBtB,gBACAK,EAAEL"}
+10
View File
@@ -0,0 +1,10 @@
/**
* Enrolled user selector module.
*
* @module mod_forum/form-user-selector
* @copyright 2019 Shamim Rezaie
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/form-user-selector",["jquery","core/ajax","core/templates"],(function($,Ajax,Templates){return{processResults:function(selector,results){var users=[];return $.each(results,(function(index,user){users.push({value:user.id,label:user._label})})),users},transport:function(selector,query,success,failure){var courseid=$(selector).attr("courseid"),contextid=$(selector).attr("data-contextid");Ajax.call([{methodname:"core_enrol_search_users",args:{courseid:courseid,search:query,searchanywhere:!0,page:0,perpage:30,contextid:contextid}}])[0].then((function(results){var promises=[],i=0;return $.each(results,(function(index,user){promises.push(Templates.render("mod_forum/form-user-selector-suggestion",user))})),$.when.apply($.when,promises).then((function(){var args=arguments;$.each(results,(function(index,user){user._label=args[i],i++})),success(results)}))})).fail(failure)}}}));
//# sourceMappingURL=form-user-selector.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"form-user-selector.min.js","sources":["../src/form-user-selector.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Enrolled user selector module.\n *\n * @module mod_forum/form-user-selector\n * @copyright 2019 Shamim Rezaie\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {\n return /** @alias module:mod_forum/form-user-selector */ {\n processResults: function(selector, results) {\n var users = [];\n $.each(results, function(index, user) {\n users.push({\n value: user.id,\n label: user._label\n });\n });\n return users;\n },\n\n transport: function(selector, query, success, failure) {\n var promise;\n var courseid = $(selector).attr('courseid');\n var contextid = $(selector).attr('data-contextid');\n\n promise = Ajax.call([{\n methodname: 'core_enrol_search_users',\n args: {\n courseid: courseid,\n search: query,\n searchanywhere: true,\n page: 0,\n perpage: 30,\n contextid: contextid,\n }\n }]);\n\n promise[0].then(function(results) {\n var promises = [],\n i = 0;\n\n // Render the label.\n $.each(results, function(index, user) {\n promises.push(Templates.render('mod_forum/form-user-selector-suggestion', user));\n });\n\n // Apply the label to the results.\n return $.when.apply($.when, promises).then(function() {\n var args = arguments;\n $.each(results, function(index, user) {\n user._label = args[i];\n i++;\n });\n success(results);\n return;\n });\n\n }).fail(failure);\n }\n\n };\n\n});\n"],"names":["define","$","Ajax","Templates","processResults","selector","results","users","each","index","user","push","value","id","label","_label","transport","query","success","failure","courseid","attr","contextid","call","methodname","args","search","searchanywhere","page","perpage","then","promises","i","render","when","apply","arguments","fail"],"mappings":";;;;;;;AAuBAA,sCAAO,CAAC,SAAU,YAAa,mBAAmB,SAASC,EAAGC,KAAMC,iBACP,CACrDC,eAAgB,SAASC,SAAUC,aAC3BC,MAAQ,UACZN,EAAEO,KAAKF,SAAS,SAASG,MAAOC,MAC5BH,MAAMI,KAAK,CACPC,MAAOF,KAAKG,GACZC,MAAOJ,KAAKK,YAGbR,OAGXS,UAAW,SAASX,SAAUY,MAAOC,QAASC,aAEtCC,SAAWnB,EAAEI,UAAUgB,KAAK,YAC5BC,UAAYrB,EAAEI,UAAUgB,KAAK,kBAEvBnB,KAAKqB,KAAK,CAAC,CACjBC,WAAY,0BACZC,KAAM,CACFL,SAAUA,SACVM,OAAQT,MACRU,gBAAgB,EAChBC,KAAM,EACNC,QAAS,GACTP,UAAWA,cAIX,GAAGQ,MAAK,SAASxB,aACjByB,SAAW,GACXC,EAAI,SAGR/B,EAAEO,KAAKF,SAAS,SAASG,MAAOC,MAC5BqB,SAASpB,KAAKR,UAAU8B,OAAO,0CAA2CvB,UAIvET,EAAEiC,KAAKC,MAAMlC,EAAEiC,KAAMH,UAAUD,MAAK,eACnCL,KAAOW,UACXnC,EAAEO,KAAKF,SAAS,SAASG,MAAOC,MAC5BA,KAAKK,OAASU,KAAKO,GACnBA,OAEJd,QAAQZ,eAIb+B,KAAKlB"}
+10
View File
@@ -0,0 +1,10 @@
/**
* Events for the forum activity.
*
* @module mod_forum/forum_events
* @copyright 2019 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/forum_events",[],(function(){return{SUBSCRIPTION_TOGGLED:"mod_forum/subscription_toggle:subscriptionToggled"}}));
//# sourceMappingURL=forum_events.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"forum_events.min.js","sources":["../src/forum_events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Events for the forum activity.\n *\n * @module mod_forum/forum_events\n * @copyright 2019 Jun Pataleta <jun@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n return {\n SUBSCRIPTION_TOGGLED: 'mod_forum/subscription_toggle:subscriptionToggled',\n };\n});\n"],"names":["define","SUBSCRIPTION_TOGGLED"],"mappings":";;;;;;;AAsBAA,gCAAO,IAAI,iBACA,CACHC,qBAAsB"}
+10
View File
@@ -0,0 +1,10 @@
define("mod_forum/grades/expandconversation",["exports","./grader/selectors","mod_forum/repository","core/notification","core/templates","core/modal_cancel","core/modal_events"],(function(_exports,ForumSelectors,_repository,_notification,_templates,_modal_cancel,ModalEvents){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* This module handles the creation of a Modal that shows the user's post in context of the entire discussion.
*
* @module mod_forum/grades/expandconversation
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.registerEventListeners=void 0,ForumSelectors=_interopRequireWildcard(ForumSelectors),_repository=_interopRequireDefault(_repository),_templates=_interopRequireDefault(_templates),_modal_cancel=_interopRequireDefault(_modal_cancel),ModalEvents=_interopRequireWildcard(ModalEvents);_exports.registerEventListeners=rootNode=>{rootNode.addEventListener("click",(e=>{const rootNode=e.target.closest(ForumSelectors.expandConversation);if(rootNode){e.preventDefault();try{!async function(rootNode){let{focusOnClose:focusOnClose=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const postId=rootNode.dataset.postid,discussionId=rootNode.dataset.discussionid,discussionName=rootNode.dataset.name,experimentalDisplayMode="1"==rootNode.dataset.experimentalDisplayMode,[allPosts,modal]=await Promise.all([_repository.default.getDiscussionPosts(parseInt(discussionId)),_modal_cancel.default.create({title:discussionName,large:!0,removeOnClose:!0,returnElement:focusOnClose})]),postsById=new Map(allPosts.posts.map((post=>(post.readonly=!0,post.hasreplies=!1,post.replies=[],[post.id,post]))));let posts=[];allPosts.posts.forEach((post=>{if(post.parentid){const parent=postsById.get(post.parentid);parent?(post.parentauthorname=parent.author.fullname,parent.hasreplies=!0,parent.replies.push(post)):posts.push(post)}else posts.push(post)})),modal.getRoot().on(ModalEvents.bodyRendered,(()=>{const relevantPost=modal.getRoot()[0].querySelector("#p".concat(postId));relevantPost&&relevantPost.scrollIntoView({behavior:"smooth"})})),modal.show();const templatePromise=_templates.default.render("mod_forum/grades/grader/discussion/post_modal",{posts:posts,experimentaldisplaymode:experimentalDisplayMode});modal.setBody(templatePromise)}(rootNode,{focusOnClose:e.target})}catch(err){(0,_notification.exception)(err)}}}))}}));
//# sourceMappingURL=expandconversation.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
define("mod_forum/grades/grader",["exports","./grader/selectors","mod_forum/repository","core/templates","../local/grades/grader","core/notification","core_course/repository","core/url"],(function(_exports,Selectors,_repository,_templates,Grader,_notification,_repository2,_url){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* This module will tie together all of the different calls the gradable module will make.
*
* @module mod_forum/grades/grader
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.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
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("mod_forum/grades/grader/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={launch:'[data-grade-action="launch"]',gradableItem:"[data-gradable-itemtype]",gradableItems:{wholeForum:'[data-gradable-itemtype="forum"]'},expandConversation:'[data-action="view-context"]',posts:'[data-region="posts"]',viewGrade:'[data-grade-action="view"]'},_exports.default}));
//# sourceMappingURL=selectors.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"selectors.min.js","sources":["../../../src/grades/grader/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/grader/selectors\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n launch: '[data-grade-action=\"launch\"]',\n gradableItem: '[data-gradable-itemtype]',\n gradableItems: {\n wholeForum: '[data-gradable-itemtype=\"forum\"]',\n },\n expandConversation: '[data-action=\"view-context\"]',\n posts: '[data-region=\"posts\"]',\n viewGrade: '[data-grade-action=\"view\"]',\n};\n"],"names":["launch","gradableItem","gradableItems","wholeForum","expandConversation","posts","viewGrade"],"mappings":"mLAsBe,CACXA,OAAQ,+BACRC,aAAc,2BACdC,cAAe,CACXC,WAAY,oCAEhBC,mBAAoB,+BACpBC,MAAO,wBACPC,UAAW"}
+10
View File
@@ -0,0 +1,10 @@
/**
* This module handles the in page replying to forum posts.
*
* @module mod_forum/inpage_reply
* @copyright 2019 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/inpage_reply",["jquery","core/templates","core/notification","mod_forum/repository","mod_forum/selectors","core_form/changechecker"],(function($,Templates,Notification,Repository,Selectors,FormChangeChecker){var DISPLAYCONSTANTS_NESTED_V2=4,DISPLAYCONSTANTS_THREADED=2,DISPLAYCONSTANTS_NESTED=3,DISPLAYCONSTANTS_FLAT_NEWEST_FIRST=-1,EVENTS={POST_CREATED:"mod_forum-post-created"},CONTENT_FORMATS={MOODLE:0},hideSubmitButtonLoadingIcon=function(button){var textContainer=button.find(Selectors.post.inpageSubmitBtnText),loadingIconContainer=button.find(Selectors.post.loadingIconContainer);button.css("width",""),textContainer.removeClass("hidden"),loadingIconContainer.addClass("hidden")},registerEventListeners=function(root){root.on("click",Selectors.post.inpageSubmitBtn,(function(e){e.preventDefault();var newid,button,textContainer,loadingIconContainer,width,submitButton=$(e.currentTarget),allButtons=submitButton.parent().find(Selectors.post.inpageReplyButton),form=submitButton.parents(Selectors.post.inpageReplyForm).get(0),message=form.elements.post.value.trim(),messageformat=CONTENT_FORMATS.MOODLE,postid=form.elements.reply.value,subject=form.elements.subject.value,currentRoot=submitButton.closest(Selectors.post.post),isprivatereply=null!=form.elements.privatereply&&form.elements.privatereply.checked,modeSelector=root.find(Selectors.post.modeSelect),mode=modeSelector.length?parseInt(modeSelector.get(0).value):null;message.length&&(textContainer=(button=submitButton).find(Selectors.post.inpageSubmitBtnText),loadingIconContainer=button.find(Selectors.post.loadingIconContainer),width=button.outerWidth(),button.css("width",width),textContainer.addClass("hidden"),loadingIconContainer.removeClass("hidden"),allButtons.prop("disabled",!0),Repository.addDiscussionPost(postid,subject,message,messageformat,isprivatereply,!0).then((function(context){var message=context.messages.reduce((function(carry,message){return"success"==message.type&&(carry+="<p>"+message.message+"</p>"),carry}),"");return Notification.addNotification({message:message,type:"success"}),context})).then((function(context){form.reset();var post=context.post;switch(newid=post.id,mode){case DISPLAYCONSTANTS_NESTED_V2:var capabilities=post.capabilities,currentAuthorName=currentRoot.children().not(Selectors.post.repliesContainer).find(Selectors.post.authorName).text();return post.parentauthorname=currentAuthorName,post.showactionmenu=capabilities.view||capabilities.controlreadstatus||capabilities.edit||capabilities.split||capabilities.delete||capabilities.export||post.urls.viewparent,Templates.render("mod_forum/forum_discussion_nested_v2_post_reply",post);case DISPLAYCONSTANTS_THREADED:return Templates.render("mod_forum/forum_discussion_threaded_post",post);case DISPLAYCONSTANTS_NESTED:return Templates.render("mod_forum/forum_discussion_nested_post",post);default:return Templates.render("mod_forum/forum_discussion_post",post)}})).then((function(html,js){var repliesnode=currentRoot.find(Selectors.post.repliesContainer).first();return mode==DISPLAYCONSTANTS_FLAT_NEWEST_FIRST?Templates.prependNodeContents(repliesnode,html,js):Templates.appendNodeContents(repliesnode,html,js)})).then((function(){return submitButton.trigger(EVENTS.POST_CREATED,newid),hideSubmitButtonLoadingIcon(submitButton),allButtons.prop("disabled",!1),FormChangeChecker.resetFormDirtyState(submitButton[0]),currentRoot.find(Selectors.post.inpageReplyContent).hide()})).then((function(){location.href="#p"+newid,location.reload()})).catch((function(error){return hideSubmitButtonLoadingIcon(submitButton),allButtons.prop("disabled",!1),Notification.exception(error)})))})),root.on("click",Selectors.post.inpageCancelButton,(function(e){FormChangeChecker.resetFormDirtyState(e.currentTarget)}))};return{init:function(root){registerEventListeners(root)},CONTENT_FORMATS:CONTENT_FORMATS,EVENTS:EVENTS}}));
//# sourceMappingURL=inpage_reply.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
define("mod_forum/local/grades/local/grader/gradingpanel",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};
/**
* Grading panel functions.
*
* @module mod_forum/local/grades/local/grader/gradingpanel
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/return _exports.default=async(component,context,gradingComponent,gradingSubtype,itemName)=>{let gradingMethodHandler="".concat(gradingComponent,"/grades/grader/gradingpanel");gradingSubtype&&(gradingMethodHandler+="/".concat(gradingSubtype));const GradingMethod=await("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([gradingMethodHandler],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(gradingMethodHandler)):Promise.resolve(_systemImportTransformerGlobalIdentifier[gradingMethodHandler]));return{getter:userId=>GradingMethod.fetchCurrentGrade(component,context,itemName,userId),setter:(userId,notifyStudent,formData)=>GradingMethod.storeCurrentGrade(component,context,itemName,userId,notifyStudent,formData)}},_exports.default}));
//# sourceMappingURL=gradingpanel.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"gradingpanel.min.js","sources":["../../../../../src/local/grades/local/grader/gradingpanel.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Grading panel functions.\n *\n * @module mod_forum/local/grades/local/grader/gradingpanel\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Get the grade panel setter and getter for the current component.\n * This function dynamically pulls the relevant gradingpanel JS file defined in the grading method.\n * We do this because we do not know until execution time what the grading type is and we do not want to import unused files.\n *\n * @method\n * @param {String} component The component being graded\n * @param {Number} context The contextid of the thing being graded\n * @param {String} gradingComponent The thing providing the grading type\n * @param {String} gradingSubtype The subtype fo the grading component\n * @param {String} itemName The name of the thing being graded\n * @return {Object}\n */\nexport default async(component, context, gradingComponent, gradingSubtype, itemName) => {\n let gradingMethodHandler = `${gradingComponent}/grades/grader/gradingpanel`;\n if (gradingSubtype) {\n gradingMethodHandler += `/${gradingSubtype}`;\n }\n\n const GradingMethod = await import(gradingMethodHandler);\n\n return {\n getter: (userId) => GradingMethod.fetchCurrentGrade(component, context, itemName, userId),\n setter: (userId, notifyStudent, formData) => GradingMethod.storeCurrentGrade(\n component, context, itemName, userId, notifyStudent, formData),\n };\n};\n\n"],"names":["async","component","context","gradingComponent","gradingSubtype","itemName","gradingMethodHandler","GradingMethod","getter","userId","fetchCurrentGrade","setter","notifyStudent","formData","storeCurrentGrade"],"mappings":";;;;;;;6BAoCeA,MAAMC,UAAWC,QAASC,iBAAkBC,eAAgBC,gBACnEC,+BAA0BH,gDAC1BC,iBACAE,iCAA4BF,uBAG1BG,oOAA6BD,gYAAAA,8BAE5B,CACHE,OAASC,QAAWF,cAAcG,kBAAkBT,UAAWC,QAASG,SAAUI,QAClFE,OAAQ,CAACF,OAAQG,cAAeC,WAAaN,cAAcO,kBACvDb,UAAWC,QAASG,SAAUI,OAAQG,cAAeC"}
@@ -0,0 +1,11 @@
define("mod_forum/local/grades/local/grader/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;
/**
* Define all of the selectors we will be using on the grading interface.
*
* @module mod_forum/local/grades/local/grader/selectors
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const getDataSelector=(name,value)=>"[data-".concat(name,'="').concat(value,'"]');var _default={buttons:{toggleFullscreen:getDataSelector("action","togglefullscreen"),closeGrader:getDataSelector("action","closegrader"),collapseGradingDrawer:getDataSelector("action","collapse-grading-drawer"),saveGrade:getDataSelector("action","savegrade"),selectUser:getDataSelector("action","select-user"),toggleSearch:getDataSelector("action","toggle-search")},regions:{bodyContainer:getDataSelector("region","body-container"),moduleContainer:getDataSelector("region","module_content_container"),moduleReplace:getDataSelector("region","module_content"),pickerRegion:getDataSelector("region","user_picker"),gradingInfoContainer:getDataSelector("region","grading-info-container"),gradingPanel:getDataSelector("region","grade"),gradingPanelContainer:getDataSelector("region","grading-panel-container"),gradingPanelErrors:getDataSelector("region","grade-errors"),searchResultsContainer:getDataSelector("region","search-results-container"),statusContainer:getDataSelector("region","status-container"),userSearchContainer:getDataSelector("region","user-search-container"),userSearchInput:getDataSelector("region","user-search-input")},values:{sendStudentNotifications:'[data-region="notification"] input[type="radio"]:checked'}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=selectors.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"selectors.min.js","sources":["../../../../../src/local/grades/local/grader/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Define all of the selectors we will be using on the grading interface.\n *\n * @module mod_forum/local/grades/local/grader/selectors\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n buttons: {\n toggleFullscreen: getDataSelector('action', 'togglefullscreen'),\n closeGrader: getDataSelector('action', 'closegrader'),\n collapseGradingDrawer: getDataSelector('action', 'collapse-grading-drawer'),\n saveGrade: getDataSelector('action', 'savegrade'),\n selectUser: getDataSelector('action', 'select-user'),\n toggleSearch: getDataSelector('action', 'toggle-search')\n },\n regions: {\n bodyContainer: getDataSelector('region', 'body-container'),\n moduleContainer: getDataSelector('region', 'module_content_container'),\n moduleReplace: getDataSelector('region', 'module_content'),\n pickerRegion: getDataSelector('region', 'user_picker'),\n gradingInfoContainer: getDataSelector('region', 'grading-info-container'),\n gradingPanel: getDataSelector('region', 'grade'),\n gradingPanelContainer: getDataSelector('region', 'grading-panel-container'),\n gradingPanelErrors: getDataSelector('region', 'grade-errors'),\n searchResultsContainer: getDataSelector('region', 'search-results-container'),\n statusContainer: getDataSelector('region', 'status-container'),\n userSearchContainer: getDataSelector('region', 'user-search-container'),\n userSearchInput: getDataSelector('region', 'user-search-input')\n },\n values: {\n sendStudentNotifications: '[data-region=\"notification\"] input[type=\"radio\"]:checked',\n }\n};\n\n"],"names":["getDataSelector","name","value","buttons","toggleFullscreen","closeGrader","collapseGradingDrawer","saveGrade","selectUser","toggleSearch","regions","bodyContainer","moduleContainer","moduleReplace","pickerRegion","gradingInfoContainer","gradingPanel","gradingPanelContainer","gradingPanelErrors","searchResultsContainer","statusContainer","userSearchContainer","userSearchInput","values","sendStudentNotifications"],"mappings":";;;;;;;;MA6BMA,gBAAkB,CAACC,KAAMC,wBACXD,kBAASC,yBAGd,CACXC,QAAS,CACLC,iBAAkBJ,gBAAgB,SAAU,oBAC5CK,YAAaL,gBAAgB,SAAU,eACvCM,sBAAuBN,gBAAgB,SAAU,2BACjDO,UAAWP,gBAAgB,SAAU,aACrCQ,WAAYR,gBAAgB,SAAU,eACtCS,aAAcT,gBAAgB,SAAU,kBAE5CU,QAAS,CACLC,cAAeX,gBAAgB,SAAU,kBACzCY,gBAAiBZ,gBAAgB,SAAU,4BAC3Ca,cAAeb,gBAAgB,SAAU,kBACzCc,aAAcd,gBAAgB,SAAU,eACxCe,qBAAsBf,gBAAgB,SAAU,0BAChDgB,aAAchB,gBAAgB,SAAU,SACxCiB,sBAAuBjB,gBAAgB,SAAU,2BACjDkB,mBAAoBlB,gBAAgB,SAAU,gBAC9CmB,uBAAwBnB,gBAAgB,SAAU,4BAClDoB,gBAAiBpB,gBAAgB,SAAU,oBAC3CqB,oBAAqBrB,gBAAgB,SAAU,yBAC/CsB,gBAAiBtB,gBAAgB,SAAU,sBAE/CuB,OAAQ,CACJC,yBAA0B"}
@@ -0,0 +1,10 @@
define("mod_forum/local/grades/local/grader/user_picker",["exports","core/templates","./user_picker/selectors","core/str"],(function(_exports,_templates,_selectors,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* This module will tie together all of the different calls the gradable module will make.
*
* @module mod_forum/local/grades/local/grader/user_picker
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_selectors=_interopRequireDefault(_selectors);class UserPicker{constructor(userList,showUserCallback,preChangeUserCallback){this.userList=userList,this.showUserCallback=showUserCallback,this.preChangeUserCallback=preChangeUserCallback,this.currentUserIndex=0,this.render=this.render.bind(this),this.setUserId=this.setUserId.bind(this)}setUserId(userId){const userIndex=this.userList.findIndex((user=>user.id===parseInt(userId)));if(-1===userIndex)throw Error("User with id ".concat(userId," not found"));this.currentUserIndex=userIndex}async render(){this.root=document.createElement("div");const{html:html,js:js}=await this.renderNavigator();_templates.default.replaceNodeContents(this.root,html,js),await this.showUser(this.currentUser),this.registerEventListeners()}renderNavigator(){return _templates.default.renderForPromise("".concat("mod_forum/local/grades/local/grader","/user_picker"),{})}renderUserChange(context){return _templates.default.renderForPromise("".concat("mod_forum/local/grades/local/grader","/user_picker/user"),context)}async showUser(user){const[{html:html,js:js}]=await Promise.all([this.renderUserChange(user),this.showUserCallback(user)]),userRegion=this.root.querySelector(_selectors.default.regions.userRegion);_templates.default.replaceNodeContents(userRegion,html,js);this.root.querySelector(_selectors.default.regions.currentUser).textContent=await(0,_str.getString)("nowgradinguser","mod_forum",user.fullname)}registerEventListeners(){this.root.addEventListener("click",(async e=>{const button=e.target.closest(_selectors.default.actions.changeUser);if(button){(await this.preChangeUserCallback(this.currentUser)).failed||(this.updateIndex(parseInt(button.dataset.direction)),await this.showUser(this.currentUser))}}))}updateIndex(direction){return this.currentUserIndex+=direction,this.currentUserIndex<0?this.currentUserIndex=this.userList.length-1:this.currentUserIndex>this.userList.length-1&&(this.currentUserIndex=0),this.currentUserIndex}get currentUser(){return{...this.userList[this.currentUserIndex],total:this.userList.length,displayIndex:this.currentUserIndex+1}}get rootNode(){return this.root}}return _exports.default=async function(users,showUserCallback,preChangeUserCallback){let{initialUserId:initialUserId=null}=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};const userPicker=new UserPicker(users,showUserCallback,preChangeUserCallback);return initialUserId&&userPicker.setUserId(initialUserId),await userPicker.render(),userPicker},_exports.default}));
//# sourceMappingURL=user_picker.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
define("mod_forum/local/grades/local/grader/user_picker/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={regions:{currentUser:'[data-region="user_picker/current_user"]',userRegion:'[data-region="user_picker/user"]'},actions:{changeUser:'[data-action="change-user"]'}},_exports.default}));
//# sourceMappingURL=selectors.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"selectors.min.js","sources":["../../../../../../src/local/grades/local/grader/user_picker/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Define all of the selectors we will be using on the grading interface.\n *\n * @module mod_forum/local/grades/local/grader/user_picker/selectors\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n regions: {\n currentUser: '[data-region=\"user_picker/current_user\"]',\n userRegion: '[data-region=\"user_picker/user\"]',\n },\n actions: {\n changeUser: '[data-action=\"change-user\"]',\n }\n};\n\n"],"names":["regions","currentUser","userRegion","actions","changeUser"],"mappings":"2MAuBe,CACXA,QAAS,CACLC,YAAa,2CACbC,WAAY,oCAEhBC,QAAS,CACLC,WAAY"}
+10
View File
@@ -0,0 +1,10 @@
define("mod_forum/local/layout/fullscreen",["exports","core/loadingicon","core/toast","core/local/aria/focuslock"],(function(_exports,_loadingicon,_toast,FocusLockManager){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.createLayout=void 0,FocusLockManager=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}
/**
* Full screen window layout.
*
* @module mod_forum/local/layout/fullscreen
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/(FocusLockManager);_exports.createLayout=function(){let{fullscreen:fullscreen=!0,showLoader:showLoader=!1,focusOnClose:focusOnClose=null}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const container=document.createElement("div");document.body.append(container),container.classList.add("layout"),container.classList.add("fullscreen"),container.setAttribute("role","application"),(0,_toast.addToastRegion)(container),lockBodyScroll(),FocusLockManager.trapFocus(container);const helpers=getLayoutHelpers(container,FocusLockManager,focusOnClose);return showLoader&&helpers.showLoadingIcon(),fullscreen&&helpers.requestFullscreen(),helpers};const getLayoutHelpers=(layoutNode,FocusLockManager,focusOnClose)=>{const contentNode=document.createElement("div");layoutNode.append(contentNode);const loadingNode=document.createElement("div");layoutNode.append(loadingNode);const requestFullscreen=()=>{layoutNode.requestFullscreen?layoutNode.requestFullscreen():layoutNode.msRequestFullscreen?layoutNode.msRequestFullscreen():layoutNode.mozRequestFullscreen?layoutNode.mozRequestFullscreen():layoutNode.webkitRequestFullscreen?layoutNode.webkitRequestFullscreen():layoutNode.setTop(0)},exitFullscreen=()=>{if(document.exitRequestFullScreen){if(document.fullScreenElement!==layoutNode)return;document.exitRequestFullScreen()}else if(document.msExitFullscreen){if(document.msFullscreenElement!==layoutNode)return;document.msExitFullscreen()}else if(document.mozCancelFullScreen){if(document.mozFullScreenElement!==layoutNode)return;document.mozCancelFullScreen()}else if(document.webkitExitFullscreen){if(document.webkitFullscreenElement!==layoutNode)return;document.webkitExitFullscreen()}},hideLoadingIcon=()=>{let child=loadingNode.lastElementChild;for(;child;)loadingNode.removeChild(child),child=loadingNode.lastElementChild};return{close:()=>{if(exitFullscreen(),unlockBodyScroll(),FocusLockManager.untrapFocus(),layoutNode.remove(),focusOnClose)try{focusOnClose.focus()}catch(e){}},toggleFullscreen:()=>{document.exitRequestFullScreen?document.fullScreenElement===layoutNode?exitFullscreen():requestFullscreen():document.msExitFullscreen?document.msFullscreenElement===layoutNode?exitFullscreen():requestFullscreen():document.mozCancelFullScreen?document.mozFullScreenElement===layoutNode?exitFullscreen():requestFullscreen():document.webkitExitFullscreen&&(document.webkitFullscreenElement===layoutNode?exitFullscreen():requestFullscreen())},requestFullscreen:requestFullscreen,exitFullscreen:exitFullscreen,getContainer:()=>contentNode,setContent:content=>{hideLoadingIcon();let child=contentNode.lastElementChild;for(;child;)contentNode.removeChild(child),child=contentNode.lastElementChild;contentNode.append(content)},showLoadingIcon:()=>{(0,_loadingicon.addIconToContainer)(loadingNode)},hideLoadingIcon:hideLoadingIcon}},lockBodyScroll=()=>{document.querySelector("body").classList.add("overflow-hidden")},unlockBodyScroll=()=>{document.querySelector("body").classList.remove("overflow-hidden")}}));
//# sourceMappingURL=fullscreen.min.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("mod_forum/local/layouts",["exports","./layout/fullscreen"],(function(_exports,_fullscreen){Object.defineProperty(_exports,"__esModule",{value:!0}),Object.defineProperty(_exports,"createFullScreenWindow",{enumerable:!0,get:function(){return _fullscreen.createLayout}})}));
//# sourceMappingURL=layouts.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"layouts.min.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
+10
View File
@@ -0,0 +1,10 @@
/**
* Handle the manual locking of individual discussions
*
* @module mod_forum/lock_toggle
* @copyright 2019 Peter Dias <peter@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/lock_toggle",["jquery","core/templates","core/notification","mod_forum/repository","mod_forum/selectors"],(function($,Templates,Notification,Repository,Selectors){return{init:function(root,preventDefault){root.on("click",Selectors.lock.toggle,(function(e){var toggleElement=$(this),forumId=toggleElement.data("forumid"),discussionId=toggleElement.data("discussionid"),state=toggleElement.data("state");Repository.setDiscussionLockState(forumId,discussionId,state).then((function(){return location.reload()})).catch(Notification.exception),preventDefault&&e.preventDefault()}))}}}));
//# sourceMappingURL=lock_toggle.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"lock_toggle.min.js","sources":["../src/lock_toggle.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Handle the manual locking of individual discussions\n *\n * @module mod_forum/lock_toggle\n * @copyright 2019 Peter Dias <peter@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/templates',\n 'core/notification',\n 'mod_forum/repository',\n 'mod_forum/selectors',\n ], function(\n $,\n Templates,\n Notification,\n Repository,\n Selectors\n ) {\n\n /**\n * Register event listeners for the subscription toggle.\n *\n * @param {object} root The discussion list root element\n * @param {boolean} preventDefault Should the default action of the event be prevented\n */\n var registerEventListeners = function(root, preventDefault) {\n root.on('click', Selectors.lock.toggle, function(e) {\n var toggleElement = $(this);\n var forumId = toggleElement.data('forumid');\n var discussionId = toggleElement.data('discussionid');\n var state = toggleElement.data('state');\n\n Repository.setDiscussionLockState(forumId, discussionId, state)\n .then(function() {\n return location.reload();\n })\n .catch(Notification.exception);\n\n if (preventDefault) {\n e.preventDefault();\n }\n });\n };\n\n return {\n init: registerEventListeners\n };\n});\n"],"names":["define","$","Templates","Notification","Repository","Selectors","init","root","preventDefault","on","lock","toggle","e","toggleElement","this","forumId","data","discussionId","state","setDiscussionLockState","then","location","reload","catch","exception"],"mappings":";;;;;;;AAsBAA,+BAAO,CACC,SACA,iBACA,oBACA,uBACA,wBACD,SACCC,EACAC,UACAC,aACAC,WACAC,iBA4BG,CACHC,KApByB,SAASC,KAAMC,gBACxCD,KAAKE,GAAG,QAASJ,UAAUK,KAAKC,QAAQ,SAASC,OACzCC,cAAgBZ,EAAEa,MAClBC,QAAUF,cAAcG,KAAK,WAC7BC,aAAeJ,cAAcG,KAAK,gBAClCE,MAAQL,cAAcG,KAAK,SAE/BZ,WAAWe,uBAAuBJ,QAASE,aAAcC,OACpDE,MAAK,kBACKC,SAASC,YAEnBC,MAAMpB,aAAaqB,WAEpBhB,gBACAI,EAAEJ"}
+14
View File
@@ -0,0 +1,14 @@
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module mod_forum/pin_toggle
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/pin_toggle",["jquery","core/ajax","core/str","core/templates","core/notification","mod_forum/repository","mod_forum/selectors","core/str"],(function($,Ajax,Str,Templates,Notification,Repository,Selectors,String){return{init:function(root,preventDefault,callback){root.on("click",Selectors.pin.toggle,(function(e){var toggleElement=$(this),forumid=toggleElement.data("forumid"),discussionid=toggleElement.data("discussionid"),pinstate=toggleElement.data("targetstate");Repository.setPinDiscussionState(forumid,discussionid,pinstate).then((function(context){return callback(toggleElement,context)})).then((function(){return String.get_string("pinupdated","forum").done((function(s){return Notification.addNotification({message:s,type:"info"})}))})).fail(Notification.exception),preventDefault&&e.preventDefault()}))}}}));
//# sourceMappingURL=pin_toggle.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"pin_toggle.min.js","sources":["../src/pin_toggle.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * This module is the highest level module for the calendar. It is\n * responsible for initialising all of the components required for\n * the calendar to run. It also coordinates the interaction between\n * components by listening for and responding to different events\n * triggered within the calendar UI.\n *\n * @module mod_forum/pin_toggle\n * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/ajax',\n 'core/str',\n 'core/templates',\n 'core/notification',\n 'mod_forum/repository',\n 'mod_forum/selectors',\n 'core/str',\n], function(\n $,\n Ajax,\n Str,\n Templates,\n Notification,\n Repository,\n Selectors,\n String\n) {\n\n /**\n * Registery event listeners for the pin toggle.\n *\n * @param {object} root The calendar root element\n * @param {boolean} preventDefault Should the default action of the event be prevented\n * @param {function} callback Success callback\n */\n var registerEventListeners = function(root, preventDefault, callback) {\n root.on('click', Selectors.pin.toggle, function(e) {\n var toggleElement = $(this);\n var forumid = toggleElement.data('forumid');\n var discussionid = toggleElement.data('discussionid');\n var pinstate = toggleElement.data('targetstate');\n Repository.setPinDiscussionState(forumid, discussionid, pinstate)\n .then(function(context) {\n return callback(toggleElement, context);\n })\n .then(function() {\n return String.get_string(\"pinupdated\", \"forum\")\n .done(function(s) {\n return Notification.addNotification({\n message: s,\n type: \"info\"\n });\n });\n })\n .fail(Notification.exception);\n\n if (preventDefault) {\n e.preventDefault();\n }\n });\n };\n\n return {\n init: registerEventListeners\n };\n});"],"names":["define","$","Ajax","Str","Templates","Notification","Repository","Selectors","String","init","root","preventDefault","callback","on","pin","toggle","e","toggleElement","this","forumid","data","discussionid","pinstate","setPinDiscussionState","then","context","get_string","done","s","addNotification","message","type","fail","exception"],"mappings":";;;;;;;;;;;AA0BAA,8BAAO,CACH,SACA,YACA,WACA,iBACA,oBACA,uBACA,sBACA,aACD,SACCC,EACAC,KACAC,IACAC,UACAC,aACAC,WACAC,UACAC,cAqCO,CACHC,KA5ByB,SAASC,KAAMC,eAAgBC,UACxDF,KAAKG,GAAG,QAASN,UAAUO,IAAIC,QAAQ,SAASC,OACxCC,cAAgBhB,EAAEiB,MAClBC,QAAUF,cAAcG,KAAK,WAC7BC,aAAeJ,cAAcG,KAAK,gBAClCE,SAAWL,cAAcG,KAAK,eAClCd,WAAWiB,sBAAsBJ,QAASE,aAAcC,UACnDE,MAAK,SAASC,gBACJb,SAASK,cAAeQ,YAElCD,MAAK,kBACKhB,OAAOkB,WAAW,aAAc,SAClCC,MAAK,SAASC,UACJvB,aAAawB,gBAAgB,CAChCC,QAASF,EACTG,KAAM,eAIrBC,KAAK3B,aAAa4B,WAEnBtB,gBACAK,EAAEL"}
+14
View File
@@ -0,0 +1,14 @@
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module mod_forum/posts_list
* @copyright 2019 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/posts_list",["jquery","core/templates","core/notification","core/pending","mod_forum/selectors","mod_forum/inpage_reply","core_form/changechecker"],(function($,Templates,Notification,Pending,Selectors,InPageReply,FormChangeChecker){return{init:function(root,throttlingwarningmsg){!function(root,throttlingwarningmsg){root.on("click",Selectors.post.inpageReplyLink,(function(e){if(e.preventDefault(),window.location.hash){var url=window.location.href.split("#")[0];history.pushState({},document.title,url)}var pending=new Pending("inpage-reply"),currentTarget=$(e.currentTarget).parents(Selectors.post.forumCoreContent),currentSubject=currentTarget.find(Selectors.post.forumSubject),currentRoot=$(e.currentTarget).parents(Selectors.post.forumContent),context={postid:$(currentRoot).data("post-id"),reply_url:$(e.currentTarget).attr("href"),sesskey:M.cfg.sesskey,parentsubject:currentSubject.data("replySubject"),canreplyprivately:$(e.currentTarget).data("can-reply-privately"),postformat:InPageReply.CONTENT_FORMATS.MOODLE,throttlingwarningmsg:throttlingwarningmsg};if(currentRoot.find(Selectors.post.inpageReplyContent).length){var form=currentRoot.find(Selectors.post.inpageReplyContent);form.slideToggle(300,pending.resolve),form.is(":visible")&&form.find("textarea").focus()}else Templates.render("mod_forum/inpage_reply",context).then((function(html,js){return Templates.appendNodeContents(currentTarget,html,js)})).then((function(){return currentRoot.find(Selectors.post.inpageReplyContent).slideToggle(300,pending.resolve).find("textarea").focus()})).then((function(){FormChangeChecker.watchFormById("inpage-reply-".concat(context.postid))})).catch(Notification.exception)}))}(root,throttlingwarningmsg),InPageReply.init(root)}}}));
//# sourceMappingURL=posts_list.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Forum repository class to encapsulate all of the AJAX requests that subscribe or unsubscribe
* can be sent for forum.
*
* @module mod_forum/repository
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/repository",["core/ajax"],(function(Ajax){return{setDiscussionSubscriptionState:function(forumId,discussionId,targetState){var request={methodname:"mod_forum_set_subscription_state",args:{forumid:forumId,discussionid:discussionId,targetstate:targetState}};return Ajax.call([request])[0]},addDiscussionPost:function(postid,subject,message,messageformat,isprivatereply,topreferredformat){var request={methodname:"mod_forum_add_discussion_post",args:{postid:postid,message:message,messageformat:messageformat,subject:subject,options:[{name:"private",value:isprivatereply},{name:"topreferredformat",value:topreferredformat}]}};return Ajax.call([request])[0]},setDiscussionLockState:function(forumId,discussionId,targetState){var request={methodname:"mod_forum_set_lock_state",args:{forumid:forumId,discussionid:discussionId,targetstate:targetState}};return Ajax.call([request])[0]},setFavouriteDiscussionState:function(forumId,discussionId,targetState){var request={methodname:"mod_forum_toggle_favourite_state",args:{discussionid:discussionId,targetstate:targetState}};return Ajax.call([request])[0]},setPinDiscussionState:function(forumid,discussionid,targetstate){var request={methodname:"mod_forum_set_pin_state",args:{discussionid:discussionid,targetstate:targetstate}};return Ajax.call([request])[0]},getDiscussionByUserID:function(userid,cmid){let sortby=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"modified",sortdirection=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"DESC";var request={methodname:"mod_forum_get_discussion_posts_by_userid",args:{userid:userid,cmid:cmid,sortby:sortby,sortdirection:sortdirection}};return Ajax.call([request])[0]},getDiscussionPosts:function(discussionId){let sortby=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"created",sortdirection=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"ASC";var request={methodname:"mod_forum_get_discussion_posts",args:{discussionid:discussionId,sortby:sortby,sortdirection:sortdirection}};return Ajax.call([request])[0]}}}));
//# sourceMappingURL=repository.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* Common CSS selectors for the forum UI.
*
* @module mod_forum/selectors
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/selectors",[],(function(){return{subscription:{toggle:"[data-type='subscription-toggle'][data-action='toggle']"},summary:{actions:"[data-container='discussion-summary-actions']"},post:{post:'[data-region="post"]',action:'[data-region="post-action"]',actionsContainer:'[data-region="post-actions-container"]',authorName:'[data-region="author-name"]',forumCoreContent:"[data-region-content='forum-post-core']",forumContent:"[data-content='forum-post']",forumSubject:"[data-region-content='forum-post-core-subject']",inpageCancelButton:"button[name='cancelbtn']",inpageReplyButton:"button",inpageReplyLink:"[data-action='collapsible-link']",inpageReplyCancelButton:"[data-action='cancel-inpage-reply']",inpageReplyCreateButton:"[data-action='create-inpage-reply']",inpageReplyContainer:'[data-region="inpage-reply-container"]',inpageReplyContent:"[data-content='inpage-reply-content']",inpageReplyForm:"form[data-content='inpage-reply-form']",inpageSubmitBtn:"[data-action='forum-inpage-submit']",inpageSubmitBtnText:"[data-region='submit-text']",loadingIconContainer:"[data-region='loading-icon-container']",repliesContainer:"[data-region='replies-container']",replyCount:'[data-region="reply-count"]',modeSelect:"select[name='mode']",showReplies:'[data-action="show-replies"]',hideReplies:'[data-action="hide-replies"]',repliesVisibilityToggleContainer:'[data-region="replies-visibility-toggle-container"]'},lock:{toggle:"[data-action='toggle'][data-type='lock-toggle']",icon:"[data-region='locked-icon']"},favourite:{toggle:"[data-type='favorite-toggle'][data-action='toggle']"},pin:{toggle:"[data-type='pin-toggle'][data-action='toggle']"},discussion:{tools:'[data-container="discussion-tools"]',item:'[data-region="discussion-list-item"]',lockedLabel:"[data-region='locked-label']",subscribedLabel:"[data-region='subscribed-label']",timedLabel:"[data-region='timed-label']"}}}));
//# sourceMappingURL=selectors.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Common CSS selectors for the forum UI.\n *\n * @module mod_forum/selectors\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n return {\n subscription: {\n toggle: \"[data-type='subscription-toggle'][data-action='toggle']\",\n },\n summary: {\n actions: \"[data-container='discussion-summary-actions']\"\n },\n post: {\n post: '[data-region=\"post\"]',\n action: '[data-region=\"post-action\"]',\n actionsContainer: '[data-region=\"post-actions-container\"]',\n authorName: '[data-region=\"author-name\"]',\n forumCoreContent: \"[data-region-content='forum-post-core']\",\n forumContent: \"[data-content='forum-post']\",\n forumSubject: \"[data-region-content='forum-post-core-subject']\",\n inpageCancelButton: \"button[name='cancelbtn']\",\n inpageReplyButton: \"button\",\n inpageReplyLink: \"[data-action='collapsible-link']\",\n inpageReplyCancelButton: \"[data-action='cancel-inpage-reply']\",\n inpageReplyCreateButton: \"[data-action='create-inpage-reply']\",\n inpageReplyContainer: '[data-region=\"inpage-reply-container\"]',\n inpageReplyContent: \"[data-content='inpage-reply-content']\",\n inpageReplyForm: \"form[data-content='inpage-reply-form']\",\n inpageSubmitBtn: \"[data-action='forum-inpage-submit']\",\n inpageSubmitBtnText: \"[data-region='submit-text']\",\n loadingIconContainer: \"[data-region='loading-icon-container']\",\n repliesContainer: \"[data-region='replies-container']\",\n replyCount: '[data-region=\"reply-count\"]',\n modeSelect: \"select[name='mode']\",\n showReplies: '[data-action=\"show-replies\"]',\n hideReplies: '[data-action=\"hide-replies\"]',\n repliesVisibilityToggleContainer: '[data-region=\"replies-visibility-toggle-container\"]'\n },\n lock: {\n toggle: \"[data-action='toggle'][data-type='lock-toggle']\",\n icon: \"[data-region='locked-icon']\"\n },\n favourite: {\n toggle: \"[data-type='favorite-toggle'][data-action='toggle']\",\n },\n pin: {\n toggle: \"[data-type='pin-toggle'][data-action='toggle']\",\n },\n discussion: {\n tools: '[data-container=\"discussion-tools\"]',\n item: '[data-region=\"discussion-list-item\"]',\n lockedLabel: \"[data-region='locked-label']\",\n subscribedLabel: \"[data-region='subscribed-label']\",\n timedLabel: \"[data-region='timed-label']\",\n },\n };\n});\n"],"names":["define","subscription","toggle","summary","actions","post","action","actionsContainer","authorName","forumCoreContent","forumContent","forumSubject","inpageCancelButton","inpageReplyButton","inpageReplyLink","inpageReplyCancelButton","inpageReplyCreateButton","inpageReplyContainer","inpageReplyContent","inpageReplyForm","inpageSubmitBtn","inpageSubmitBtnText","loadingIconContainer","repliesContainer","replyCount","modeSelect","showReplies","hideReplies","repliesVisibilityToggleContainer","lock","icon","favourite","pin","discussion","tools","item","lockedLabel","subscribedLabel","timedLabel"],"mappings":";;;;;;;AAsBAA,6BAAO,IAAI,iBACA,CACHC,aAAc,CACVC,OAAQ,2DAEZC,QAAS,CACLC,QAAS,iDAEbC,KAAM,CACFA,KAAM,uBACNC,OAAQ,8BACRC,iBAAkB,yCAClBC,WAAY,8BACZC,iBAAkB,0CAClBC,aAAc,8BACdC,aAAc,kDACdC,mBAAoB,2BACpBC,kBAAmB,SACnBC,gBAAiB,mCACjBC,wBAAyB,sCACzBC,wBAAyB,sCACzBC,qBAAsB,yCACtBC,mBAAoB,wCACpBC,gBAAiB,yCACjBC,gBAAiB,sCACjBC,oBAAqB,8BACrBC,qBAAsB,yCACtBC,iBAAkB,oCAClBC,WAAY,8BACZC,WAAY,sBACZC,YAAa,+BACbC,YAAa,+BACbC,iCAAkC,uDAEtCC,KAAM,CACF3B,OAAQ,kDACR4B,KAAM,+BAEVC,UAAW,CACP7B,OAAQ,uDAEZ8B,IAAK,CACD9B,OAAQ,kDAEZ+B,WAAY,CACRC,MAAO,sCACPC,KAAM,uCACNC,YAAa,+BACbC,gBAAiB,mCACjBC,WAAY"}
+11
View File
@@ -0,0 +1,11 @@
/**
* Handle discussion subscription toggling on a discussion list in
* the forum view.
*
* @module mod_forum/subscription_toggle
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_forum/subscription_toggle",["jquery","core/templates","core/notification","mod_forum/repository","mod_forum/selectors","core/pubsub","mod_forum/forum_events"],(function($,Templates,Notification,Repository,Selectors,PubSub,ForumEvents){return{init:function(root,preventDefault,callback){root.on("click",Selectors.subscription.toggle,(function(e){var toggleElement=$(this),forumId=toggleElement.data("forumid"),discussionId=toggleElement.data("discussionid"),subscriptionState=toggleElement.data("targetstate");Repository.setDiscussionSubscriptionState(forumId,discussionId,subscriptionState).then((function(context){return PubSub.publish(ForumEvents.SUBSCRIPTION_TOGGLED,{discussionId:discussionId,subscriptionState:subscriptionState}),callback(toggleElement,context)})).catch(Notification.exception),preventDefault&&e.preventDefault()}))}}}));
//# sourceMappingURL=subscription_toggle.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"subscription_toggle.min.js","sources":["../src/subscription_toggle.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Handle discussion subscription toggling on a discussion list in\n * the forum view.\n *\n * @module mod_forum/subscription_toggle\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/templates',\n 'core/notification',\n 'mod_forum/repository',\n 'mod_forum/selectors',\n 'core/pubsub',\n 'mod_forum/forum_events',\n ], function(\n $,\n Templates,\n Notification,\n Repository,\n Selectors,\n PubSub,\n ForumEvents\n ) {\n\n /**\n * Register event listeners for the subscription toggle.\n *\n * @param {object} root The discussion list root element\n * @param {boolean} preventDefault Should the default action of the event be prevented\n * @param {function} callback Success callback\n */\n var registerEventListeners = function(root, preventDefault, callback) {\n root.on('click', Selectors.subscription.toggle, function(e) {\n var toggleElement = $(this);\n var forumId = toggleElement.data('forumid');\n var discussionId = toggleElement.data('discussionid');\n var subscriptionState = toggleElement.data('targetstate');\n\n Repository.setDiscussionSubscriptionState(forumId, discussionId, subscriptionState)\n .then(function(context) {\n PubSub.publish(ForumEvents.SUBSCRIPTION_TOGGLED, {\n discussionId: discussionId,\n subscriptionState: subscriptionState\n });\n return callback(toggleElement, context);\n })\n .catch(Notification.exception);\n\n if (preventDefault) {\n e.preventDefault();\n }\n });\n };\n\n return {\n init: registerEventListeners\n };\n});\n"],"names":["define","$","Templates","Notification","Repository","Selectors","PubSub","ForumEvents","init","root","preventDefault","callback","on","subscription","toggle","e","toggleElement","this","forumId","data","discussionId","subscriptionState","setDiscussionSubscriptionState","then","context","publish","SUBSCRIPTION_TOGGLED","catch","exception"],"mappings":";;;;;;;;AAuBAA,uCAAO,CACC,SACA,iBACA,oBACA,uBACA,sBACA,cACA,2BACD,SACCC,EACAC,UACAC,aACAC,WACAC,UACAC,OACAC,mBAiCG,CACHC,KAxByB,SAASC,KAAMC,eAAgBC,UACxDF,KAAKG,GAAG,QAASP,UAAUQ,aAAaC,QAAQ,SAASC,OACjDC,cAAgBf,EAAEgB,MAClBC,QAAUF,cAAcG,KAAK,WAC7BC,aAAeJ,cAAcG,KAAK,gBAClCE,kBAAoBL,cAAcG,KAAK,eAE3Cf,WAAWkB,+BAA+BJ,QAASE,aAAcC,mBAC5DE,MAAK,SAASC,gBACXlB,OAAOmB,QAAQlB,YAAYmB,qBAAsB,CAC7CN,aAAcA,aACdC,kBAAmBA,oBAEhBV,SAASK,cAAeQ,YAElCG,MAAMxB,aAAayB,WAEpBlB,gBACAK,EAAEL"}
+291
View File
@@ -0,0 +1,291 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module for viewing a discussion.
*
* @module mod_forum/discussion
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/custom_interaction_events',
'mod_forum/selectors',
'core/pubsub',
'mod_forum/forum_events',
'core/str',
'core/notification',
],
function(
$,
CustomEvents,
Selectors,
PubSub,
ForumEvents,
String,
Notification
) {
/**
* Set the focus on the previous post in the list. Previous post is calculated
* based on position in list as viewed top to bottom.
*
* @param {Object} currentPost The post that currently has focus
*/
var focusPreviousPost = function(currentPost) {
// See if there is a previous sibling post.
var prevPost = currentPost.prev(Selectors.post.post);
if (prevPost.length) {
// The previous post might have replies that appear visually between
// it and the current post (see nested view) so if that's the case
// then the last reply will be the previous post in the list.
var replyPost = prevPost.find(Selectors.post.post).last();
if (replyPost.length) {
// Focus the last reply.
replyPost.focus();
} else {
// No replies so we can focus straight on the sibling.
prevPost.focus();
}
} else {
// If there are no siblings then jump up the tree to the parent
// post and focus the first parent post we find.
currentPost.parents(Selectors.post.post).first().focus();
}
};
/**
* Set the focus on the next post in the list. Previous post is calculated
* based on position in list as viewed top to bottom.
*
* @param {Object} currentPost The post that currently has focus
*/
var focusNextPost = function(currentPost) {
// The next post in the visual list would be the first reply to this one
// so let's see if we have one.
var replyPost = currentPost.find(Selectors.post.post).first();
if (replyPost.length) {
// Got a reply.
replyPost.focus();
} else {
// If we don't have a reply then the next post in the visual list would
// be a sibling post (replying to the same parent).
var siblingPost = currentPost.next(Selectors.post.post);
if (siblingPost.length) {
siblingPost.focus();
} else {
// No siblings either. That means we're the lowest level reply in a thread
// so we need to walk back up the tree of posts and find an ancestor post that
// has a sibling post we can focus.
var parentPosts = currentPost.parents(Selectors.post.post).toArray();
for (var i = 0; i < parentPosts.length; i++) {
var ancestorSiblingPost = $(parentPosts[i]).next(Selectors.post.post);
if (ancestorSiblingPost.length) {
ancestorSiblingPost.focus();
break;
}
}
}
}
};
/**
* Check if the element is inside the in page reply section.
*
* @param {Object} element The element to check
* @return {Boolean}
*/
var isElementInInPageReplySection = function(element) {
var inPageReply = $(element).closest(Selectors.post.inpageReplyContent);
return inPageReply.length ? true : false;
};
/**
* Initialise the keyboard accessibility controls for the discussion.
*
* @param {Object} root The discussion root element
*/
var initAccessibilityKeyboardNav = function(root) {
var posts = root.find(Selectors.post.post);
// Take each post action out of the tab index.
posts.each(function(index, post) {
var actions = $(post).find(Selectors.post.action);
var firstAction = actions.first();
actions.attr('tabindex', '-1');
firstAction.attr('tabindex', 0);
});
CustomEvents.define(root, [
CustomEvents.events.up,
CustomEvents.events.down,
CustomEvents.events.next,
CustomEvents.events.previous,
CustomEvents.events.home,
CustomEvents.events.end,
]);
root.on(CustomEvents.events.up, function(e, data) {
var activeElement = document.activeElement;
if (isElementInInPageReplySection(activeElement)) {
// Focus is currently inside the in page reply section so don't move focus
// to another post.
return;
}
var focusPost = $(activeElement).closest(Selectors.post.post);
if (focusPost.length) {
focusPreviousPost(focusPost);
} else {
root.find(Selectors.post.post).first().focus();
}
data.originalEvent.preventDefault();
});
root.on(CustomEvents.events.down, function(e, data) {
var activeElement = document.activeElement;
if (isElementInInPageReplySection(activeElement)) {
// Focus is currently inside the in page reply section so don't move focus
// to another post.
return;
}
var focusPost = $(activeElement).closest(Selectors.post.post);
if (focusPost.length) {
focusNextPost(focusPost);
} else {
root.find(Selectors.post.post).first().focus();
}
data.originalEvent.preventDefault();
});
root.on(CustomEvents.events.home, function(e, data) {
if (isElementInInPageReplySection(document.activeElement)) {
// Focus is currently inside the in page reply section so don't move focus
// to another post.
return;
}
root.find(Selectors.post.post).first().focus();
data.originalEvent.preventDefault();
});
root.on(CustomEvents.events.end, function(e, data) {
if (isElementInInPageReplySection(document.activeElement)) {
// Focus is currently inside the in page reply section so don't move focus
// to another post.
return;
}
root.find(Selectors.post.post).last().focus();
data.originalEvent.preventDefault();
});
root.on(CustomEvents.events.next, Selectors.post.action, function(e, data) {
var currentAction = $(e.target);
var container = currentAction.closest(Selectors.post.actionsContainer);
var actions = container.find(Selectors.post.action);
var nextAction = currentAction.next(Selectors.post.action);
actions.attr('tabindex', '-1');
if (!nextAction.length) {
nextAction = actions.first();
}
nextAction.attr('tabindex', 0);
nextAction.focus();
data.originalEvent.preventDefault();
});
root.on(CustomEvents.events.previous, Selectors.post.action, function(e, data) {
var currentAction = $(e.target);
var container = currentAction.closest(Selectors.post.actionsContainer);
var actions = container.find(Selectors.post.action);
var nextAction = currentAction.prev(Selectors.post.action);
actions.attr('tabindex', '-1');
if (!nextAction.length) {
nextAction = actions.last();
}
nextAction.attr('tabindex', 0);
nextAction.focus();
data.originalEvent.preventDefault();
});
root.on(CustomEvents.events.home, Selectors.post.action, function(e, data) {
var currentAction = $(e.target);
var container = currentAction.closest(Selectors.post.actionsContainer);
var actions = container.find(Selectors.post.action);
var firstAction = actions.first();
actions.attr('tabindex', '-1');
firstAction.attr('tabindex', 0);
firstAction.focus();
e.stopPropagation();
data.originalEvent.preventDefault();
});
root.on(CustomEvents.events.end, Selectors.post.action, function(e, data) {
var currentAction = $(e.target);
var container = currentAction.closest(Selectors.post.actionsContainer);
var actions = container.find(Selectors.post.action);
var lastAction = actions.last();
actions.attr('tabindex', '-1');
lastAction.attr('tabindex', 0);
lastAction.focus();
e.stopPropagation();
data.originalEvent.preventDefault();
});
PubSub.subscribe(ForumEvents.SUBSCRIPTION_TOGGLED, function(data) {
var subscribed = data.subscriptionState;
var updateMessage = subscribed ? 'discussionsubscribed' : 'discussionunsubscribed';
String.get_string(updateMessage, "forum")
.then(function(s) {
return Notification.addNotification({
message: s,
type: "info"
});
})
.catch(Notification.exception);
});
};
return {
init: function(root) {
initAccessibilityKeyboardNav(root);
}
};
});
+151
View File
@@ -0,0 +1,151 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module for the list of discussions on when viewing a forum.
*
* @module mod_forum/discussion_list
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/templates',
'core/str',
'core/notification',
'mod_forum/subscription_toggle',
'mod_forum/selectors',
'mod_forum/repository',
'core/pubsub',
'mod_forum/forum_events',
'core_form/changechecker',
], function(
$,
Templates,
Str,
Notification,
SubscriptionToggle,
Selectors,
Repository,
PubSub,
ForumEvents,
FormChangeChecker
) {
var registerEventListeners = function(root) {
PubSub.subscribe(ForumEvents.SUBSCRIPTION_TOGGLED, function(data) {
var discussionId = data.discussionId;
var subscribed = data.subscriptionState;
var discussionListItem = root.find(Selectors.discussion.item + '[data-discussionid= ' + discussionId + ']');
var subscribedLabel = discussionListItem.find(Selectors.discussion.subscribedLabel);
if (subscribed) {
discussionListItem.addClass('subscribed');
subscribedLabel.removeAttr('hidden');
} else {
discussionListItem.removeClass('subscribed');
subscribedLabel.attr('hidden', true);
}
});
root.on('click', Selectors.post.inpageCancelButton, function(e) {
// Tell formchangechecker to reset the form state.
FormChangeChecker.resetFormDirtyState(e.currentTarget);
});
root.on('click', Selectors.favourite.toggle, function(e) {
e.preventDefault();
var toggleElement = $(this);
var forumId = toggleElement.data('forumid');
var discussionId = toggleElement.data('discussionid');
var subscriptionState = toggleElement.data('targetstate');
Repository.setFavouriteDiscussionState(forumId, discussionId, subscriptionState)
.then(function() {
return location.reload();
})
.catch(Notification.exception);
});
root.on('click', Selectors.pin.toggle, function(e) {
e.preventDefault();
var toggleElement = $(this);
var forumId = toggleElement.data('forumid');
var discussionId = toggleElement.data('discussionid');
var state = toggleElement.data('targetstate');
Repository.setPinDiscussionState(forumId, discussionId, state)
.then(function() {
return location.reload();
})
.catch(Notification.exception);
});
root.on('click', Selectors.lock.toggle, function(e) {
var toggleElement = $(this);
var forumId = toggleElement.data('forumid');
var discussionId = toggleElement.data('discussionid');
var state = toggleElement.data('state');
Repository.setDiscussionLockState(forumId, discussionId, state)
.then(function(context) {
var icon = toggleElement.parents(Selectors.summary.actions).find(Selectors.lock.icon);
var lockedLabel = toggleElement.parents(Selectors.discussion.item).find(Selectors.discussion.lockedLabel);
if (context.locked) {
icon.removeClass('hidden');
lockedLabel.removeAttr('hidden');
} else {
icon.addClass('hidden');
lockedLabel.attr('hidden', true);
}
return context;
})
.then(function(context) {
context.forumid = forumId;
return Templates.render('mod_forum/discussion_lock_toggle', context);
})
.then(function(html, js) {
return Templates.replaceNode(toggleElement, html, js);
})
.then(function() {
return Str.get_string('lockupdated', 'forum')
.done(function(s) {
return Notification.addNotification({
message: s,
type: "info"
});
});
})
.catch(Notification.exception);
e.preventDefault();
});
};
return {
init: function(root) {
SubscriptionToggle.init(root, false, function(toggleElement, context) {
var toggleId = toggleElement.attr('id');
var newTargetState = context.userstate.subscribed ? 0 : 1;
toggleElement.data('targetstate', newTargetState);
var stringKey = context.userstate.subscribed ? 'unsubscribediscussion' : 'subscribediscussion';
return Str.get_string(stringKey, 'mod_forum')
.then(function(string) {
toggleElement.closest('td').find('label[for="' + toggleId + '"]').find('span').text(string);
return string;
});
});
registerEventListeners(root);
}
};
});
+430
View File
@@ -0,0 +1,430 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module for viewing a discussion in nested v2 view.
*
* @module mod_forum/discussion_nested_v2
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import AutoRows from 'core/auto_rows';
import CustomEvents from 'core/custom_interaction_events';
import * as FormChangeChecker from 'core_form/changechecker';
import Notification from 'core/notification';
import Templates from 'core/templates';
import Discussion from 'mod_forum/discussion';
import InPageReply from 'mod_forum/inpage_reply';
import LockToggle from 'mod_forum/lock_toggle';
import FavouriteToggle from 'mod_forum/favourite_toggle';
import Pin from 'mod_forum/pin_toggle';
import Selectors from 'mod_forum/selectors';
import Subscribe from 'mod_forum/subscription_toggle';
const ANIMATION_DURATION = 150;
/**
* Get the closest post container element from the given element.
*
* @param {Object} element jQuery element to search from
* @return {Object} jQuery element
*/
const getPostContainer = (element) => {
return element.closest(Selectors.post.post);
};
/**
* Get the closest post container element from the given element.
*
* @param {Object} element jQuery element to search from
* @param {Number} id Id of the post to find.
* @return {Object} jQuery element
*/
const getPostContainerById = (element, id) => {
return element.find(`${Selectors.post.post}[data-post-id=${id}]`);
};
/**
* Get the parent post container elements from the given element.
*
* @param {Object} element jQuery element to search from
* @return {Object} jQuery element
*/
const getParentPostContainers = (element) => {
return element.parents(Selectors.post.post);
};
/**
* Get the post content container element from the post container element.
*
* @param {Object} postContainer jQuery element for the post container
* @return {Object} jQuery element
*/
const getPostContentContainer = (postContainer) => {
return postContainer.children().not(Selectors.post.repliesContainer).find(Selectors.post.forumCoreContent);
};
/**
* Get the in page reply container element from the post container element.
*
* @param {Object} postContainer jQuery element for the post container
* @return {Object} jQuery element
*/
const getInPageReplyContainer = (postContainer) => {
return postContainer.children().filter(Selectors.post.inpageReplyContainer);
};
/**
* Get the in page reply form element from the post container element.
*
* @param {Object} postContainer jQuery element for the post container
* @return {Object} jQuery element
*/
const getInPageReplyForm = (postContainer) => {
return getInPageReplyContainer(postContainer).find(Selectors.post.inpageReplyContent);
};
/**
* Get the in page reply create (reply) button element from the post container element.
*
* @param {Object} postContainer jQuery element for the post container
* @return {Object} jQuery element
*/
const getInPageReplyCreateButton = (postContainer) => {
return getPostContentContainer(postContainer).find(Selectors.post.inpageReplyCreateButton);
};
/**
* Get the replies visibility toggle container (show/hide replies button container) element
* from the post container element.
*
* @param {Object} postContainer jQuery element for the post container
* @return {Object} jQuery element
*/
const getRepliesVisibilityToggleContainer = (postContainer) => {
return postContainer.children(Selectors.post.repliesVisibilityToggleContainer);
};
/**
* Get the replies container element from the post container element.
*
* @param {Object} postContainer jQuery element for the post container
* @return {Object} jQuery element
*/
const getRepliesContainer = (postContainer) => {
return postContainer.children(Selectors.post.repliesContainer);
};
/**
* Check if the post has any replies.
*
* @param {Object} postContainer jQuery element for the post container
* @return {Bool}
*/
const hasReplies = (postContainer) => {
return getRepliesContainer(postContainer).children().length > 0;
};
/**
* Get the show replies button element from the replies visibility toggle container element.
*
* @param {Object} replyVisibilityToggleContainer jQuery element for the toggle container
* @return {Object} jQuery element
*/
const getShowRepliesButton = (replyVisibilityToggleContainer) => {
return replyVisibilityToggleContainer.find(Selectors.post.showReplies);
};
/**
* Get the hide replies button element from the replies visibility toggle container element.
*
* @param {Object} replyVisibilityToggleContainer jQuery element for the toggle container
* @return {Object} jQuery element
*/
const getHideRepliesButton = (replyVisibilityToggleContainer) => {
return replyVisibilityToggleContainer.find(Selectors.post.hideReplies);
};
/**
* Check if the replies are visible.
*
* @param {Object} postContainer jQuery element for the post container
* @return {Bool}
*/
const repliesVisible = (postContainer) => {
const repliesContainer = getRepliesContainer(postContainer);
return repliesContainer.is(':visible');
};
/**
* Show the post replies.
*
* @param {Object} postContainer jQuery element for the post container
* @param {Number|null} postIdToSee Id of the post to scroll into view (if any)
*/
const showReplies = (postContainer, postIdToSee = null) => {
const repliesContainer = getRepliesContainer(postContainer);
const replyVisibilityToggleContainer = getRepliesVisibilityToggleContainer(postContainer);
const showButton = getShowRepliesButton(replyVisibilityToggleContainer);
const hideButton = getHideRepliesButton(replyVisibilityToggleContainer);
showButton.addClass('hidden');
hideButton.removeClass('hidden');
repliesContainer.slideDown({
duration: ANIMATION_DURATION,
queue: false,
complete: () => {
if (postIdToSee) {
const postContainerToSee = getPostContainerById(repliesContainer, postIdToSee);
if (postContainerToSee.length) {
postContainerToSee[0].scrollIntoView();
}
}
}
}).css('display', 'none').fadeIn(ANIMATION_DURATION);
};
/**
* Hide the post replies.
*
* @param {Object} postContainer jQuery element for the post container
*/
const hideReplies = (postContainer) => {
const repliesContainer = getRepliesContainer(postContainer);
const replyVisibilityToggleContainer = getRepliesVisibilityToggleContainer(postContainer);
const showButton = getShowRepliesButton(replyVisibilityToggleContainer);
const hideButton = getHideRepliesButton(replyVisibilityToggleContainer);
showButton.removeClass('hidden');
hideButton.addClass('hidden');
repliesContainer.slideUp({
duration: ANIMATION_DURATION,
queue: false
}).fadeOut(ANIMATION_DURATION);
};
/** Variable to hold the showInPageReplyForm function after it's built. */
let showInPageReplyForm = null;
/**
* Build the showInPageReplyForm function with the given additional template context.
*
* @param {Object} additionalTemplateContext Additional render context for the in page reply template.
* @return {Function}
*/
const buildShowInPageReplyFormFunction = (additionalTemplateContext) => {
/**
* Show the in page reply form in the given in page reply container. The form
* display will be animated.
*
* @param {Object} postContainer jQuery element for the post container
*/
return async(postContainer) => {
const inPageReplyContainer = getInPageReplyContainer(postContainer);
const repliesVisibilityToggleContainer = getRepliesVisibilityToggleContainer(postContainer);
const inPageReplyCreateButton = getInPageReplyCreateButton(postContainer);
if (!hasInPageReplyForm(inPageReplyContainer)) {
try {
const html = await renderInPageReplyTemplate(additionalTemplateContext, inPageReplyCreateButton, postContainer);
Templates.appendNodeContents(inPageReplyContainer, html, '');
} catch (e) {
Notification.exception(e);
}
FormChangeChecker.watchForm(postContainer[0].querySelector('form'));
}
inPageReplyCreateButton.fadeOut(ANIMATION_DURATION, () => {
const inPageReplyForm = getInPageReplyForm(postContainer);
inPageReplyForm.slideDown({
duration: ANIMATION_DURATION,
queue: false,
complete: () => {
inPageReplyForm.find('textarea').focus();
}
}).css('display', 'none').fadeIn(ANIMATION_DURATION);
if (repliesVisibilityToggleContainer.length && hasReplies(postContainer)) {
repliesVisibilityToggleContainer.fadeIn(ANIMATION_DURATION);
hideReplies(postContainer);
}
});
};
};
/**
* Hide the in page reply form in the given in page reply container. The form
* display will be animated.
*
* @param {Object} postContainer jQuery element for the post container
* @param {Number|null} postIdToSee Id of the post to scroll into view (if any)
*/
const hideInPageReplyForm = (postContainer, postIdToSee = null) => {
const inPageReplyForm = getInPageReplyForm(postContainer);
const inPageReplyCreateButton = getInPageReplyCreateButton(postContainer);
const repliesVisibilityToggleContainer = getRepliesVisibilityToggleContainer(postContainer);
if (repliesVisibilityToggleContainer.length && hasReplies(postContainer)) {
repliesVisibilityToggleContainer.fadeOut(ANIMATION_DURATION);
if (!repliesVisible(postContainer)) {
showReplies(postContainer, postIdToSee);
}
}
inPageReplyForm.slideUp({
duration: ANIMATION_DURATION,
queue: false,
complete: () => {
inPageReplyCreateButton.fadeIn(ANIMATION_DURATION);
}
}).fadeOut(200);
};
/**
* Check if the in page reply container contains the in page reply form.
*
* @param {Object} inPageReplyContainer jQuery element for the in page reply container
* @return {Bool}
*/
const hasInPageReplyForm = (inPageReplyContainer) => {
return inPageReplyContainer.find(Selectors.post.inpageReplyContent).length > 0;
};
/**
* Render the template to generate the in page reply form HTML.
*
* @param {Object} additionalTemplateContext Additional render context for the in page reply template
* @param {Object} button jQuery element for the reply button that was clicked
* @param {Object} postContainer jQuery element for the post container
* @return {Object} jQuery promise
*/
const renderInPageReplyTemplate = (additionalTemplateContext, button, postContainer) => {
const postContentContainer = getPostContentContainer(postContainer);
const currentSubject = postContentContainer.find(Selectors.post.forumSubject).text();
const currentAuthorName = postContentContainer.find(Selectors.post.authorName).text();
const context = {
postid: postContainer.data('post-id'),
"reply_url": button.attr('data-href'),
sesskey: M.cfg.sesskey,
parentsubject: currentSubject,
parentauthorname: currentAuthorName,
canreplyprivately: button.data('can-reply-privately'),
postformat: InPageReply.CONTENT_FORMATS.MOODLE,
...additionalTemplateContext
};
return Templates.render('mod_forum/inpage_reply_v2', context);
};
/**
* Increment the total reply count in the show/hide replies buttons for the post.
*
* @param {Object} postContainer jQuery element for the post container
*/
const incrementTotalReplyCount = (postContainer) => {
getRepliesVisibilityToggleContainer(postContainer).find(Selectors.post.replyCount).each((index, element) => {
const currentCount = parseInt(element.innerText, 10);
element.innerText = currentCount + 1;
});
};
/**
* Create all of the event listeners for the discussion.
*
* @param {Object} root jQuery element for the discussion container
*/
const registerEventListeners = (root) => {
CustomEvents.define(root, [CustomEvents.events.activate]);
// Auto expanding text area for in page reply.
AutoRows.init(root);
// Reply button is clicked.
root.on(CustomEvents.events.activate, Selectors.post.inpageReplyCreateButton, (e, data) => {
data.originalEvent.preventDefault();
const postContainer = getPostContainer($(e.currentTarget));
showInPageReplyForm(postContainer);
});
// Cancel in page reply button.
root.on(CustomEvents.events.activate, Selectors.post.inpageReplyCancelButton, (e, data) => {
data.originalEvent.preventDefault();
const postContainer = getPostContainer($(e.currentTarget));
hideInPageReplyForm(postContainer);
});
// Show replies button clicked.
root.on(CustomEvents.events.activate, Selectors.post.showReplies, (e, data) => {
data.originalEvent.preventDefault();
const postContainer = getPostContainer($(e.target));
showReplies(postContainer);
});
// Hide replies button clicked.
root.on(CustomEvents.events.activate, Selectors.post.hideReplies, (e, data) => {
data.originalEvent.preventDefault();
const postContainer = getPostContainer($(e.target));
hideReplies(postContainer);
});
// Post created with in page reply.
root.on(InPageReply.EVENTS.POST_CREATED, Selectors.post.inpageSubmitBtn, (e, newPostId) => {
const currentTarget = $(e.currentTarget);
const postContainer = getPostContainer(currentTarget);
const postContainers = getParentPostContainers(currentTarget);
hideInPageReplyForm(postContainer, newPostId);
postContainers.each((index, container) => {
incrementTotalReplyCount($(container));
});
});
};
/**
* Initialise the javascript for the discussion in nested v2 display mode.
*
* @param {Object} root jQuery element for the discussion container
* @param {Object} context Additional render context for the in page reply template
*/
export const init = (root, context) => {
// Build the showInPageReplyForm function with the additional render context.
showInPageReplyForm = buildShowInPageReplyFormFunction(context);
// Add discussion event listeners.
registerEventListeners(root);
// Initialise default discussion javascript (keyboard nav etc).
Discussion.init(root);
// Add in page reply javascript.
InPageReply.init(root);
// Initialise the settings menu javascript.
const discussionToolsContainer = root.find(Selectors.discussion.tools);
LockToggle.init(discussionToolsContainer, false);
FavouriteToggle.init(discussionToolsContainer, false, (toggleElement, response) => {
const newTargetState = response.userstate.favourited ? 0 : 1;
return toggleElement.data('targetstate', newTargetState);
});
Pin.init(discussionToolsContainer, false, (toggleElement, response) => {
const newTargetState = response.pinned ? 0 : 1;
return toggleElement.data('targetstate', newTargetState);
});
Subscribe.init(discussionToolsContainer, false, (toggleElement, response) => {
const newTargetState = response.userstate.subscribed ? 0 : 1;
toggleElement.data('targetstate', newTargetState);
});
};
+78
View File
@@ -0,0 +1,78 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handle discussion subscription toggling on a discussion list in
* the forum view.
*
* @module mod_forum/favourite_toggle
* @copyright 2019 Peter Dias <peter@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/templates',
'core/notification',
'mod_forum/repository',
'mod_forum/selectors',
'core/str',
], function(
$,
Templates,
Notification,
Repository,
Selectors,
String
) {
/**
* Register event listeners for the subscription toggle.
*
* @param {object} root The discussion list root element
* @param {boolean} preventDefault Should the default action of the event be prevented
* @param {function} callback Success callback
*/
var registerEventListeners = function(root, preventDefault, callback) {
root.on('click', Selectors.favourite.toggle, function(e) {
var toggleElement = $(this);
var forumId = toggleElement.data('forumid');
var discussionId = toggleElement.data('discussionid');
var subscriptionState = toggleElement.data('targetstate');
Repository.setFavouriteDiscussionState(forumId, discussionId, subscriptionState)
.then(function(context) {
return callback(toggleElement, context);
})
.then(function() {
return String.get_string("favouriteupdated", "forum")
.done(function(s) {
return Notification.addNotification({
message: s,
type: "info"
});
});
})
.catch(Notification.exception);
if (preventDefault) {
e.preventDefault();
}
});
};
return {
init: registerEventListeners
};
});
+79
View File
@@ -0,0 +1,79 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Enrolled user selector module.
*
* @module mod_forum/form-user-selector
* @copyright 2019 Shamim Rezaie
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
return /** @alias module:mod_forum/form-user-selector */ {
processResults: function(selector, results) {
var users = [];
$.each(results, function(index, user) {
users.push({
value: user.id,
label: user._label
});
});
return users;
},
transport: function(selector, query, success, failure) {
var promise;
var courseid = $(selector).attr('courseid');
var contextid = $(selector).attr('data-contextid');
promise = Ajax.call([{
methodname: 'core_enrol_search_users',
args: {
courseid: courseid,
search: query,
searchanywhere: true,
page: 0,
perpage: 30,
contextid: contextid,
}
}]);
promise[0].then(function(results) {
var promises = [],
i = 0;
// Render the label.
$.each(results, function(index, user) {
promises.push(Templates.render('mod_forum/form-user-selector-suggestion', user));
});
// Apply the label to the results.
return $.when.apply($.when, promises).then(function() {
var args = arguments;
$.each(results, function(index, user) {
user._label = args[i];
i++;
});
success(results);
return;
});
}).fail(failure);
}
};
});
+27
View File
@@ -0,0 +1,27 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Events for the forum activity.
*
* @module mod_forum/forum_events
* @copyright 2019 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
return {
SUBSCRIPTION_TOGGLED: 'mod_forum/subscription_toggle:subscriptionToggled',
};
});
@@ -0,0 +1,127 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module handles the creation of a Modal that shows the user's post in context of the entire discussion.
*
* @module mod_forum/grades/expandconversation
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as ForumSelectors from './grader/selectors';
import Repository from 'mod_forum/repository';
import {exception as showException} from "core/notification";
import Templates from 'core/templates';
import Modal from 'core/modal_cancel';
import * as ModalEvents from 'core/modal_events';
/**
* Find the Node containing the gradable details from the provided node by searching up the tree.
*
* @param {HTMLElement} node
* @returns {HTMLElement}
*/
const findGradableNode = node => node.closest(ForumSelectors.expandConversation);
/**
* Show the post in context in a modal.
*
* @param {HTMLElement} rootNode The button that has been clicked
* @param {object} param
* @param {bool} [param.focusOnClose=null]
*/
const showPostInContext = async(rootNode, {
focusOnClose = null,
} = {}) => {
const postId = rootNode.dataset.postid;
const discussionId = rootNode.dataset.discussionid;
const discussionName = rootNode.dataset.name;
const experimentalDisplayMode = rootNode.dataset.experimentalDisplayMode == "1";
const [
allPosts,
modal,
] = await Promise.all([
Repository.getDiscussionPosts(parseInt(discussionId)),
Modal.create({
title: discussionName,
large: true,
removeOnClose: true,
returnElement: focusOnClose,
}),
]);
const postsById = new Map(allPosts.posts.map(post => {
post.readonly = true;
post.hasreplies = false;
post.replies = [];
return [post.id, post];
}));
let posts = [];
allPosts.posts.forEach(post => {
if (post.parentid) {
const parent = postsById.get(post.parentid);
if (parent) {
post.parentauthorname = parent.author.fullname;
parent.hasreplies = true;
parent.replies.push(post);
} else {
posts.push(post);
}
} else {
posts.push(post);
}
});
modal.getRoot().on(ModalEvents.bodyRendered, () => {
const relevantPost = modal.getRoot()[0].querySelector(`#p${postId}`);
if (relevantPost) {
relevantPost.scrollIntoView({behavior: "smooth"});
}
});
modal.show();
// Note: We do not use await here because it messes with the Modal transitions.
const templatePromise = Templates.render('mod_forum/grades/grader/discussion/post_modal', {
posts,
experimentaldisplaymode: experimentalDisplayMode
});
modal.setBody(templatePromise);
};
/**
* Register event listeners for the expand conversations button.
*
* @param {HTMLElement} rootNode The root to listen to.
*/
export const registerEventListeners = (rootNode) => {
rootNode.addEventListener('click', (e) => {
const rootNode = findGradableNode(e.target);
if (rootNode) {
e.preventDefault();
try {
showPostInContext(rootNode, {
focusOnClose: e.target,
});
} catch (err) {
showException(err);
}
}
});
};
+231
View File
@@ -0,0 +1,231 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module will tie together all of the different calls the gradable module will make.
*
* @module mod_forum/grades/grader
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as Selectors from './grader/selectors';
import Repository from 'mod_forum/repository';
import Templates from 'core/templates';
import * as Grader from '../local/grades/grader';
import Notification from 'core/notification';
import CourseRepository from 'core_course/repository';
import {relativeUrl} from 'core/url';
const templateNames = {
contentRegion: 'mod_forum/grades/grader/discussion/posts',
};
/**
* Curried function with CMID set, this is then used in unified grader as a fetch a users content.
*
* @param {Number} cmid
* @param {Bool} experimentalDisplayMode
* @return {Function}
*/
const getContentForUserIdFunction = (cmid, experimentalDisplayMode) => (userid) => {
/**
* Given the parent function is called with the second param set execute the partially executed function.
*
* @param {Number} userid
*/
return Repository.getDiscussionByUserID(userid, cmid)
.then(context => {
// Rebuild the returned data for the template.
context.discussions = context.discussions.map(discussionPostMapper);
context.experimentaldisplaymode = experimentalDisplayMode ? true : false;
return Templates.render(templateNames.contentRegion, context);
})
.catch(Notification.exception);
};
/**
* 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} 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 getGradableUsersForCourseidFunction = (courseID, groupID, onlyActive) => async() => {
const context = await CourseRepository.getGradableUsersFromCourseID(courseID, groupID, onlyActive);
return context.users;
};
const findGradableNode = node => node.closest(Selectors.gradableItem);
/**
* For a discussion we need to manipulate it's posts to hide certain UI elements.
*
* @param {Object} discussion
* @return {Array} name, id, posts
*/
const discussionPostMapper = (discussion) => {
// Map postid => post.
const parentMap = new Map();
discussion.posts.parentposts.forEach(post => parentMap.set(post.id, post));
const userPosts = discussion.posts.userposts.map(post => {
post.readonly = true;
post.hasreplies = false;
post.replies = [];
const parent = post.parentid ? parentMap.get(post.parentid) : null;
if (parent) {
parent.hasreplies = false;
parent.replies = [];
parent.readonly = true;
post.parentauthorname = parent.author.fullname;
}
return {
parent,
post
};
});
return {
...discussion,
posts: userPosts,
};
};
/**
* Launch the Grader.
*
* @param {HTMLElement} rootNode the root HTML element describing what is to be graded
* @param {object} param
* @param {bool} [param.focusOnClose=null]
*/
const launchWholeForumGrading = async(rootNode, {
focusOnClose = null,
} = {}) => {
const data = rootNode.dataset;
const gradingPanelFunctions = await Grader.getGradingPanelFunctions(
'mod_forum',
data.contextid,
data.gradingComponent,
data.gradingComponentSubtype,
data.gradableItemtype
);
const groupID = data.group ? data.group : 0;
const onlyActive = data.gradeOnlyActiveUsers;
await Grader.launch(
getGradableUsersForCourseidFunction(data.courseId, groupID, onlyActive),
getContentForUserIdFunction(data.cmid, data.experimentalDisplayMode == "1"),
gradingPanelFunctions.getter,
gradingPanelFunctions.setter,
{
groupid: data.groupid,
initialUserId: data.initialuserid,
moduleName: data.name,
courseName: data.courseName,
courseUrl: relativeUrl('/course/view.php', {id: data.courseId}),
sendStudentNotifications: data.sendStudentNotifications,
focusOnClose,
}
);
};
/**
* Launch the Grader.
*
* @param {HTMLElement} rootNode the root HTML element describing what is to be graded
* @param {object} param
* @param {bool} [param.focusOnClose=null]
*/
const launchViewGrading = async(rootNode, {
focusOnClose = null,
} = {}) => {
const data = rootNode.dataset;
const gradingPanelFunctions = await Grader.getGradingPanelFunctions(
'mod_forum',
data.contextid,
data.gradingComponent,
data.gradingComponentSubtype,
data.gradableItemtype
);
await Grader.view(
gradingPanelFunctions.getter,
data.userid,
data.name,
{
focusOnClose,
}
);
};
/**
* Register listeners to launch the grading panel.
*/
export const 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)) {
// Note: The preventDefault must be before any async function calls because the function becomes async
// at that point and the default action is implemented.
e.preventDefault();
try {
await launchWholeForumGrading(rootNode, {
focusOnClose: e.target,
});
} catch (error) {
Notification.exception(error);
}
} else {
throw Error('Unable to find a valid gradable item');
}
}
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)) {
// Note: The preventDefault must be before any async function calls because the function becomes async
// at that point and the default action is implemented.
e.preventDefault();
try {
await launchViewGrading(rootNode, {
focusOnClose: e.target,
});
} catch (error) {
Notification.exception(error);
}
} else {
throw Error('Unable to find a valid gradable item');
}
}
});
};
@@ -0,0 +1,32 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module will tie together all of the different calls the gradable module will make.
*
* @module mod_forum/grades/grader/selectors
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
launch: '[data-grade-action="launch"]',
gradableItem: '[data-gradable-itemtype]',
gradableItems: {
wholeForum: '[data-gradable-itemtype="forum"]',
},
expandConversation: '[data-action="view-context"]',
posts: '[data-region="posts"]',
viewGrade: '[data-grade-action="view"]',
};
+206
View File
@@ -0,0 +1,206 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module handles the in page replying to forum posts.
*
* @module mod_forum/inpage_reply
* @copyright 2019 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/templates',
'core/notification',
'mod_forum/repository',
'mod_forum/selectors',
'core_form/changechecker',
], function(
$,
Templates,
Notification,
Repository,
Selectors,
FormChangeChecker
) {
var DISPLAYCONSTANTS = {
NESTED_V2: 4,
THREADED: 2,
NESTED: 3,
FLAT_OLDEST_FIRST: 1,
FLAT_NEWEST_FIRST: -1
};
var EVENTS = {
POST_CREATED: 'mod_forum-post-created'
};
/**
* Moodle formats taken from the FORMAT_* constants declared in lib/weblib.php.
* @type {Object}
*/
var CONTENT_FORMATS = {
MOODLE: 0
};
/**
* Show the loading icon for the submit button.
*
* @param {Object} button The submit button element
*/
var showSubmitButtonLoadingIcon = function(button) {
var textContainer = button.find(Selectors.post.inpageSubmitBtnText);
var loadingIconContainer = button.find(Selectors.post.loadingIconContainer);
var width = button.outerWidth();
// Fix the width so that the button size doesn't change when we show the loading icon.
button.css('width', width);
textContainer.addClass('hidden');
loadingIconContainer.removeClass('hidden');
};
/**
* Hide the loading icon for the submit button.
*
* @param {Object} button The submit button element
*/
var hideSubmitButtonLoadingIcon = function(button) {
var textContainer = button.find(Selectors.post.inpageSubmitBtnText);
var loadingIconContainer = button.find(Selectors.post.loadingIconContainer);
// Reset the width back to it's default.
button.css('width', '');
textContainer.removeClass('hidden');
loadingIconContainer.addClass('hidden');
};
/**
* Register the event listeners for the submit/cancel buttons of the in page reply.
*
* @param {Object} root The discussion container element.
*/
var registerEventListeners = function(root) {
root.on('click', Selectors.post.inpageSubmitBtn, function(e) {
e.preventDefault();
var submitButton = $(e.currentTarget);
var allButtons = submitButton.parent().find(Selectors.post.inpageReplyButton);
var form = submitButton.parents(Selectors.post.inpageReplyForm).get(0);
var message = form.elements.post.value.trim();
// For now, we consider the inline reply post written using the FORMAT_MOODLE (because a textarea is displayed).
// In the future, other formats should be supported, letting users to use their preferred editor and format.
var messageformat = CONTENT_FORMATS.MOODLE;
// The message post will be converted from messageformat to FORMAT_HTML.
var topreferredformat = true;
var postid = form.elements.reply.value;
var subject = form.elements.subject.value;
var currentRoot = submitButton.closest(Selectors.post.post);
var isprivatereply = form.elements.privatereply != undefined ? form.elements.privatereply.checked : false;
var modeSelector = root.find(Selectors.post.modeSelect);
var mode = modeSelector.length ? parseInt(modeSelector.get(0).value) : null;
var newid;
if (message.length) {
showSubmitButtonLoadingIcon(submitButton);
allButtons.prop('disabled', true);
Repository.addDiscussionPost(postid, subject, message, messageformat, isprivatereply, topreferredformat)
.then(function(context) {
var message = context.messages.reduce(function(carry, message) {
if (message.type == 'success') {
carry += '<p>' + message.message + '</p>';
}
return carry;
}, '');
Notification.addNotification({
message: message,
type: "success"
});
return context;
})
.then(function(context) {
form.reset();
var post = context.post;
newid = post.id;
switch (mode) {
case DISPLAYCONSTANTS.NESTED_V2:
var capabilities = post.capabilities;
var currentAuthorName = currentRoot.children()
.not(Selectors.post.repliesContainer)
.find(Selectors.post.authorName)
.text();
post.parentauthorname = currentAuthorName;
post.showactionmenu = capabilities.view ||
capabilities.controlreadstatus ||
capabilities.edit ||
capabilities.split ||
capabilities.delete ||
capabilities.export ||
post.urls.viewparent;
return Templates.render('mod_forum/forum_discussion_nested_v2_post_reply', post);
case DISPLAYCONSTANTS.THREADED:
return Templates.render('mod_forum/forum_discussion_threaded_post', post);
case DISPLAYCONSTANTS.NESTED:
return Templates.render('mod_forum/forum_discussion_nested_post', post);
default:
return Templates.render('mod_forum/forum_discussion_post', post);
}
})
.then(function(html, js) {
var repliesnode = currentRoot.find(Selectors.post.repliesContainer).first();
if (mode == DISPLAYCONSTANTS.FLAT_NEWEST_FIRST) {
return Templates.prependNodeContents(repliesnode, html, js);
} else {
return Templates.appendNodeContents(repliesnode, html, js);
}
})
.then(function() {
submitButton.trigger(EVENTS.POST_CREATED, newid);
hideSubmitButtonLoadingIcon(submitButton);
allButtons.prop('disabled', false);
// Tell formchangechecker we submitted the form.
FormChangeChecker.resetFormDirtyState(submitButton[0]);
return currentRoot.find(Selectors.post.inpageReplyContent).hide();
})
.then(function() {
location.href = "#p" + newid;
// Reload the page, say if threshold is being set by user those would get reflected through the templates.
location.reload();
})
.catch(function(error) {
hideSubmitButtonLoadingIcon(submitButton);
allButtons.prop('disabled', false);
return Notification.exception(error);
});
}
});
root.on('click', Selectors.post.inpageCancelButton, function(e) {
// Tell formchangechecker to reset the form state.
FormChangeChecker.resetFormDirtyState(e.currentTarget);
});
};
return {
init: function(root) {
registerEventListeners(root);
},
CONTENT_FORMATS: CONTENT_FORMATS,
EVENTS: EVENTS
};
});
+514
View File
@@ -0,0 +1,514 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module will tie together all of the different calls the gradable module will make.
*
* @module mod_forum/local/grades/grader
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Templates from 'core/templates';
import Selectors from './local/grader/selectors';
import getUserPicker from './local/grader/user_picker';
import {createLayout as createFullScreenWindow} from 'mod_forum/local/layout/fullscreen';
import getGradingPanelFunctions from './local/grader/gradingpanel';
import {add as addToast} from 'core/toast';
import {addNotification} from 'core/notification';
import {getString} from 'core/str';
import {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';
import {addIconToContainerWithPromise} from 'core/loadingicon';
import {debounce} from 'core/utils';
import {fillInitialValues} from 'core_grades/grades/grader/gradingpanel/comparison';
import Modal from 'core/modal_cancel';
import {subscribe} from 'core/pubsub';
import DrawerEvents from 'core/drawer_events';
const templateNames = {
grader: {
app: 'mod_forum/local/grades/grader',
gradingPanel: {
error: 'mod_forum/local/grades/local/grader/gradingpanel/error',
},
searchResults: 'mod_forum/local/grades/local/grader/user_picker/user_search',
status: 'mod_forum/local/grades/local/grader/status',
},
};
/**
* Helper function that replaces the user picker placeholder with what we get back from the user picker class.
*
* @param {HTMLElement} root
* @param {String} html
*/
const displayUserPicker = (root, html) => {
const pickerRegion = root.querySelector(Selectors.regions.pickerRegion);
Templates.replaceNodeContents(pickerRegion, html, '');
};
/**
* To be removed, this is now done as a part of Templates.renderForPromise()
*
* @param {String} html
* @param {String} js
* @returns {array} An array containing the HTML, and JS.
*/
const fetchContentFromRender = (html, js) => {
return [html, js];
};
/**
* Here we build the function that is passed to the user picker that'll handle updating the user content area
* of the grading interface.
*
* @param {HTMLElement} root
* @param {Function} getContentForUser
* @param {Function} getGradeForUser
* @param {Function} saveGradeForUser
* @return {Function}
*/
const getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser, saveGradeForUser) => {
let firstLoad = true;
return async(user) => {
const spinner = firstLoad ? null : addIconToContainerWithPromise(root);
const [
[html, js],
userGrade,
] = await Promise.all([
getContentForUser(user.id).then(fetchContentFromRender),
getGradeForUser(user.id),
]);
Templates.replaceNodeContents(root.querySelector(Selectors.regions.moduleReplace), html, js);
const [
gradingPanelHtml,
gradingPanelJS
] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);
const panelContainer = root.querySelector(Selectors.regions.gradingPanelContainer);
const panel = panelContainer.querySelector(Selectors.regions.gradingPanel);
Templates.replaceNodeContents(panel, gradingPanelHtml, gradingPanelJS);
const form = panel.querySelector('form');
fillInitialValues(form);
form.addEventListener('submit', event => {
saveGradeForUser(user);
event.preventDefault();
});
panelContainer.scrollTop = 0;
firstLoad = false;
if (spinner) {
spinner.resolve();
}
return userGrade;
};
};
/**
* Show the search results container and hide the user picker and body content.
*
* @param {HTMLElement} bodyContainer The container element for the body content
* @param {HTMLElement} userPickerContainer The container element for the user picker
* @param {HTMLElement} searchResultsContainer The container element for the search results
*/
const showSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {
bodyContainer.classList.add('hidden');
userPickerContainer.classList.add('hidden');
searchResultsContainer.classList.remove('hidden');
};
/**
* Hide the search results container and show the user picker and body content.
*
* @param {HTMLElement} bodyContainer The container element for the body content
* @param {HTMLElement} userPickerContainer The container element for the user picker
* @param {HTMLElement} searchResultsContainer The container element for the search results
*/
const hideSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {
bodyContainer.classList.remove('hidden');
userPickerContainer.classList.remove('hidden');
searchResultsContainer.classList.add('hidden');
};
/**
* Toggles the visibility of the user search.
*
* @param {HTMLElement} toggleSearchButton The button that toggles the search
* @param {HTMLElement} searchContainer The container element for the user search
* @param {HTMLElement} searchInput The input element for searching
*/
const showUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {
searchContainer.classList.remove('collapsed');
toggleSearchButton.setAttribute('aria-expanded', 'true');
toggleSearchButton.classList.add('expand');
toggleSearchButton.classList.remove('collapse');
// Hide the grading info container from screen reader.
const gradingInfoContainer = searchContainer.parentElement.querySelector(Selectors.regions.gradingInfoContainer);
gradingInfoContainer.setAttribute('aria-hidden', 'true');
// Hide the collapse grading drawer button from screen reader.
const collapseGradingDrawer = searchContainer.parentElement.querySelector(Selectors.buttons.collapseGradingDrawer);
collapseGradingDrawer.setAttribute('aria-hidden', 'true');
collapseGradingDrawer.setAttribute('tabindex', '-1');
searchInput.focus();
};
/**
* Toggles the visibility of the user search.
*
* @param {HTMLElement} toggleSearchButton The button that toggles the search
* @param {HTMLElement} searchContainer The container element for the user search
* @param {HTMLElement} searchInput The input element for searching
*/
const hideUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {
searchContainer.classList.add('collapsed');
toggleSearchButton.setAttribute('aria-expanded', 'false');
toggleSearchButton.classList.add('collapse');
toggleSearchButton.classList.remove('expand');
toggleSearchButton.focus();
// Show the grading info container to screen reader.
const gradingInfoContainer = searchContainer.parentElement.querySelector(Selectors.regions.gradingInfoContainer);
gradingInfoContainer.removeAttribute('aria-hidden');
// Show the collapse grading drawer button from screen reader.
const collapseGradingDrawer = searchContainer.parentElement.querySelector(Selectors.buttons.collapseGradingDrawer);
collapseGradingDrawer.removeAttribute('aria-hidden');
collapseGradingDrawer.setAttribute('tabindex', '0');
searchInput.value = '';
};
/**
* Find the list of users who's names include the given search term.
*
* @param {Array} userList List of users for the grader
* @param {String} searchTerm The search term to match
* @return {Array}
*/
const searchForUsers = (userList, searchTerm) => {
if (searchTerm === '') {
return userList;
}
searchTerm = searchTerm.toLowerCase();
return userList.filter((user) => {
return user.fullname.toLowerCase().includes(searchTerm);
});
};
/**
* Render the list of users in the search results area.
*
* @param {HTMLElement} searchResultsContainer The container element for search results
* @param {Array} users The list of users to display
*/
const renderSearchResults = async(searchResultsContainer, users) => {
const {html, js} = await Templates.renderForPromise(templateNames.grader.searchResults, {users});
Templates.replaceNodeContents(searchResultsContainer, html, js);
};
/**
* Add click handlers to the buttons in the header of the grading interface.
*
* @param {HTMLElement} graderLayout
* @param {Object} userPicker
* @param {Function} saveGradeFunction
* @param {Array} userList List of users for the grader.
*/
const registerEventListeners = (graderLayout, userPicker, saveGradeFunction, userList) => {
const graderContainer = graderLayout.getContainer();
const toggleSearchButton = graderContainer.querySelector(Selectors.buttons.toggleSearch);
const searchInputContainer = graderContainer.querySelector(Selectors.regions.userSearchContainer);
const searchInput = searchInputContainer.querySelector(Selectors.regions.userSearchInput);
const bodyContainer = graderContainer.querySelector(Selectors.regions.bodyContainer);
const userPickerContainer = graderContainer.querySelector(Selectors.regions.pickerRegion);
const searchResultsContainer = graderContainer.querySelector(Selectors.regions.searchResultsContainer);
graderContainer.addEventListener('click', (e) => {
if (e.target.closest(Selectors.buttons.toggleFullscreen)) {
e.stopImmediatePropagation();
e.preventDefault();
graderLayout.toggleFullscreen();
return;
}
if (e.target.closest(Selectors.buttons.closeGrader)) {
e.stopImmediatePropagation();
e.preventDefault();
graderLayout.close();
return;
}
if (e.target.closest(Selectors.buttons.saveGrade)) {
saveGradeFunction(userPicker.currentUser);
}
if (e.target.closest(Selectors.buttons.toggleSearch)) {
if (toggleSearchButton.getAttribute('aria-expanded') === 'true') {
// Search is open so let's close it.
hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);
hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);
searchResultsContainer.innerHTML = '';
} else {
// Search is closed so let's open it.
showUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);
showSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);
renderSearchResults(searchResultsContainer, userList);
}
return;
}
const selectUserButton = e.target.closest(Selectors.buttons.selectUser);
if (selectUserButton) {
const userId = selectUserButton.getAttribute('data-userid');
const user = userList.find(user => user.id == userId);
userPicker.setUserId(userId);
userPicker.showUser(user);
hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);
hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);
searchResultsContainer.innerHTML = '';
}
});
// Debounce the search input so that it only executes 300 milliseconds after the user has finished typing.
searchInput.addEventListener('input', debounce(() => {
const users = searchForUsers(userList, searchInput.value);
renderSearchResults(searchResultsContainer, users);
}, 300));
// Remove the right margin of the content container when the grading panel is hidden so that it expands to full-width.
subscribe(DrawerEvents.DRAWER_HIDDEN, (drawerRoot) => {
const gradingPanel = drawerRoot[0];
if (gradingPanel.querySelector(Selectors.regions.gradingPanel)) {
setContentContainerMargin(graderContainer, 0);
}
});
// Bring back the right margin of the content container when the grading panel is shown to give space for the grading panel.
subscribe(DrawerEvents.DRAWER_SHOWN, (drawerRoot) => {
const gradingPanel = drawerRoot[0];
if (gradingPanel.querySelector(Selectors.regions.gradingPanel)) {
setContentContainerMargin(graderContainer, gradingPanel.offsetWidth);
}
});
};
/**
* Adjusts the right margin of the content container.
*
* @param {HTMLElement} graderContainer The container for the grader app.
* @param {Number} rightMargin The right margin value.
*/
const setContentContainerMargin = (graderContainer, rightMargin) => {
const contentContainer = graderContainer.querySelector(Selectors.regions.moduleContainer);
if (contentContainer) {
contentContainer.style.marginRight = `${rightMargin}px`;
}
};
/**
* Get the function used to save a user grade.
*
* @param {HTMLElement} root The container for the grader
* @param {Function} setGradeForUser The function that will be called.
* @return {Function}
*/
const getSaveUserGradeFunction = (root, setGradeForUser) => {
return async(user) => {
try {
root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = '';
const result = await setGradeForUser(
user.id,
root.querySelector(Selectors.values.sendStudentNotifications).value,
root.querySelector(Selectors.regions.gradingPanel)
);
if (result.success) {
addToast(await getString('grades:gradesavedfor', 'mod_forum', user));
}
if (result.failed) {
displayGradingError(root, user, result.error);
}
return result;
} catch (err) {
displayGradingError(root, user, err);
return failedUpdate(err);
}
};
};
/**
* Display a grading error, typically from a failed save.
*
* @param {HTMLElement} root The container for the grader
* @param {Object} user The user who was errored
* @param {Object} err The details of the error
*/
const displayGradingError = async(root, user, err) => {
const [
{html, js},
errorString
] = await Promise.all([
Templates.renderForPromise(templateNames.grader.gradingPanel.error, {error: err}),
await getString('grades:gradesavefailed', 'mod_forum', {error: err.message, ...user}),
]);
Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanelErrors), html, js);
addToast(errorString, {type: 'warning'});
};
/**
* Launch the grader interface with the specified parameters.
*
* @param {Function} getListOfUsers A function to get the list of users
* @param {Function} getContentForUser A function to get the content for a specific user
* @param {Function} getGradeForUser A function get the grade details for a specific user
* @param {Function} setGradeForUser A function to set the grade for a specific user
* @param {Object} preferences Preferences for the launch function
* @param {Number} preferences.initialUserId
* @param {string} preferences.moduleName
* @param {string} preferences.courseName
* @param {string} preferences.courseUrl
* @param {boolean} preferences.sendStudentNotifications
* @param {null|HTMLElement} preferences.focusOnClose
*/
export const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {
initialUserId = null,
moduleName,
courseName,
courseUrl,
sendStudentNotifications,
focusOnClose = null,
} = {}) => {
// We need all of these functions to be executed in series, if one step runs before another the interface
// will not work.
// We need this promise to resolve separately so that we can avoid loading the whole interface if there are no users.
const userList = await getListOfUsers();
if (!userList.length) {
addNotification({
message: await getString('nouserstograde', 'core_grades'),
type: "error",
});
return;
}
// Now that we have confirmed there are at least some users let's boot up the grader interface.
const [
graderLayout,
{html, js},
] = await Promise.all([
createFullScreenWindow({
fullscreen: false,
showLoader: false,
focusOnClose,
}),
Templates.renderForPromise(templateNames.grader.app, {
moduleName,
courseName,
courseUrl,
drawer: {show: true},
defaultsendnotifications: sendStudentNotifications,
}),
]);
const graderContainer = graderLayout.getContainer();
const saveGradeFunction = getSaveUserGradeFunction(graderContainer, setGradeForUser);
Templates.replaceNodeContents(graderContainer, html, js);
const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser, saveGradeFunction);
const userIds = userList.map(user => user.id);
const statusContainer = graderContainer.querySelector(Selectors.regions.statusContainer);
// Fetch the userpicker for display.
const userPicker = await getUserPicker(
userList,
async(user) => {
const userGrade = await updateUserContent(user);
const renderContext = {
status: userGrade.hasgrade,
index: userIds.indexOf(user.id) + 1,
total: userList.length
};
Templates.render(templateNames.grader.status, renderContext).then(html => {
statusContainer.innerHTML = html;
return html;
}).catch();
},
saveGradeFunction,
{
initialUserId,
},
);
// Register all event listeners.
registerEventListeners(graderLayout, userPicker, saveGradeFunction, userList);
// Display the newly created user picker.
displayUserPicker(graderContainer, userPicker.rootNode);
};
/**
* Show the grade for a specific user.
*
* @param {Function} getGradeForUser A function get the grade details for a specific user
* @param {Number} userid The ID of a specific user
* @param {String} moduleName the name of the module
* @param {object} param
* @param {null|HTMLElement} param.focusOnClose
*/
export const view = async(getGradeForUser, userid, moduleName, {
focusOnClose = null,
} = {}) => {
const userGrade = await getGradeForUser(userid);
const [
modal,
gradeTemplateData
] = await Promise.all([
Modal.create({
title: moduleName,
large: true,
removeOnClose: true,
returnElement: focusOnClose,
show: true,
body: Templates.render('mod_forum/local/grades/view_grade', userGrade),
}),
renderGradeTemplate(userGrade)
]);
const bodyPromise = await modal.getBodyPromise();
const gradeReplace = bodyPromise[0].querySelector('[data-region="grade-template"]');
Templates.replaceNodeContents(gradeReplace, gradeTemplateData.html, gradeTemplateData.js);
};
const renderGradeTemplate = (userGrade) => Templates.renderForPromise(userGrade.templatename, userGrade.grade);
export {getGradingPanelFunctions};
@@ -0,0 +1,51 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Grading panel functions.
*
* @module mod_forum/local/grades/local/grader/gradingpanel
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Get the grade panel setter and getter for the current component.
* This function dynamically pulls the relevant gradingpanel JS file defined in the grading method.
* We do this because we do not know until execution time what the grading type is and we do not want to import unused files.
*
* @method
* @param {String} component The component being graded
* @param {Number} context The contextid of the thing being graded
* @param {String} gradingComponent The thing providing the grading type
* @param {String} gradingSubtype The subtype fo the grading component
* @param {String} itemName The name of the thing being graded
* @return {Object}
*/
export default async(component, context, gradingComponent, gradingSubtype, itemName) => {
let gradingMethodHandler = `${gradingComponent}/grades/grader/gradingpanel`;
if (gradingSubtype) {
gradingMethodHandler += `/${gradingSubtype}`;
}
const GradingMethod = await import(gradingMethodHandler);
return {
getter: (userId) => GradingMethod.fetchCurrentGrade(component, context, itemName, userId),
setter: (userId, notifyStudent, formData) => GradingMethod.storeCurrentGrade(
component, context, itemName, userId, notifyStudent, formData),
};
};
@@ -0,0 +1,61 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Define all of the selectors we will be using on the grading interface.
*
* @module mod_forum/local/grades/local/grader/selectors
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* A small helper function to build queryable data selectors.
* @param {String} name
* @param {String} value
* @return {string}
*/
const getDataSelector = (name, value) => {
return `[data-${name}="${value}"]`;
};
export default {
buttons: {
toggleFullscreen: getDataSelector('action', 'togglefullscreen'),
closeGrader: getDataSelector('action', 'closegrader'),
collapseGradingDrawer: getDataSelector('action', 'collapse-grading-drawer'),
saveGrade: getDataSelector('action', 'savegrade'),
selectUser: getDataSelector('action', 'select-user'),
toggleSearch: getDataSelector('action', 'toggle-search')
},
regions: {
bodyContainer: getDataSelector('region', 'body-container'),
moduleContainer: getDataSelector('region', 'module_content_container'),
moduleReplace: getDataSelector('region', 'module_content'),
pickerRegion: getDataSelector('region', 'user_picker'),
gradingInfoContainer: getDataSelector('region', 'grading-info-container'),
gradingPanel: getDataSelector('region', 'grade'),
gradingPanelContainer: getDataSelector('region', 'grading-panel-container'),
gradingPanelErrors: getDataSelector('region', 'grade-errors'),
searchResultsContainer: getDataSelector('region', 'search-results-container'),
statusContainer: getDataSelector('region', 'status-container'),
userSearchContainer: getDataSelector('region', 'user-search-container'),
userSearchInput: getDataSelector('region', 'user-search-input')
},
values: {
sendStudentNotifications: '[data-region="notification"] input[type="radio"]:checked',
}
};
@@ -0,0 +1,211 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module will tie together all of the different calls the gradable module will make.
*
* @module mod_forum/local/grades/local/grader/user_picker
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Templates from 'core/templates';
import Selectors from './user_picker/selectors';
import {getString} from 'core/str';
const templatePath = 'mod_forum/local/grades/local/grader';
/**
* The Grader User Picker.
*
* @class mod_forum/local/grades/local/grader/user_picker
*/
class UserPicker {
/**
* Constructor for the User Picker.
*
* @constructor mod_forum/local/grades/local/grader/user_picker
* @param {Array} userList List of users
* @param {Function} showUserCallback The callback used to display the user
* @param {Function} preChangeUserCallback The callback to use before changing user
*/
constructor(userList, showUserCallback, preChangeUserCallback) {
this.userList = userList;
this.showUserCallback = showUserCallback;
this.preChangeUserCallback = preChangeUserCallback;
this.currentUserIndex = 0;
// Ensure that render is bound correctly.
this.render = this.render.bind(this);
this.setUserId = this.setUserId.bind(this);
}
/**
* Set the current userid without rendering the change.
* To show the user, call showUser too.
*
* @param {Number} userId
*/
setUserId(userId) {
// Determine the current index based on the user ID.
const userIndex = this.userList.findIndex(user => {
return user.id === parseInt(userId);
});
if (userIndex === -1) {
throw Error(`User with id ${userId} not found`);
}
this.currentUserIndex = userIndex;
}
/**
* Render the user picker.
*/
async render() {
// Create the root node.
this.root = document.createElement('div');
const {html, js} = await this.renderNavigator();
Templates.replaceNodeContents(this.root, html, js);
// Call the showUser function to show the first user immediately.
await this.showUser(this.currentUser);
// Ensure that the event listeners are all bound.
this.registerEventListeners();
}
/**
* Render the navigator itself.
*
* @returns {Promise}
*/
renderNavigator() {
return Templates.renderForPromise(`${templatePath}/user_picker`, {});
}
/**
* Render the current user details for the picker.
*
* @param {Object} context The data used to render the user picker.
* @returns {Promise}
*/
renderUserChange(context) {
return Templates.renderForPromise(`${templatePath}/user_picker/user`, context);
}
/**
* Show the specified user in the picker.
*
* @param {Object} user
*/
async showUser(user) {
const [{html, js}] = await Promise.all([this.renderUserChange(user), this.showUserCallback(user)]);
const userRegion = this.root.querySelector(Selectors.regions.userRegion);
Templates.replaceNodeContents(userRegion, html, js);
// Update the hidden now-grading region so screen readers can announce the user that's currently being graded.
const currentUserRegion = this.root.querySelector(Selectors.regions.currentUser);
currentUserRegion.textContent = await getString('nowgradinguser', 'mod_forum', user.fullname);
}
/**
* Register the event listeners for the user picker.
*/
registerEventListeners() {
this.root.addEventListener('click', async(e) => {
const button = e.target.closest(Selectors.actions.changeUser);
if (button) {
const result = await this.preChangeUserCallback(this.currentUser);
if (!result.failed) {
this.updateIndex(parseInt(button.dataset.direction));
await this.showUser(this.currentUser);
}
}
});
}
/**
* Update the current user index.
*
* @param {Number} direction
* @returns {Number}}
*/
updateIndex(direction) {
this.currentUserIndex += direction;
// Loop around the edges.
if (this.currentUserIndex < 0) {
this.currentUserIndex = this.userList.length - 1;
} else if (this.currentUserIndex > this.userList.length - 1) {
this.currentUserIndex = 0;
}
return this.currentUserIndex;
}
/**
* Get the details of the user currently shown with the total number of users, and the 1-indexed count of the
* current user.
*
* @returns {Object}
*/
get currentUser() {
return {
...this.userList[this.currentUserIndex],
total: this.userList.length,
displayIndex: this.currentUserIndex + 1,
};
}
/**
* Get the root node for the User Picker.
*
* @returns {HTMLElement}
*/
get rootNode() {
return this.root;
}
}
/**
* Create a new user picker.
*
* @param {Array} users The list of users
* @param {Function} showUserCallback The function to call to show a specific user
* @param {Function} preChangeUserCallback The fucntion to call to save the grade for the current user
* @param {Number} [currentUserID] The userid of the current user
* @returns {UserPicker}
*/
export default async(
users,
showUserCallback,
preChangeUserCallback,
{
initialUserId = null,
} = {}
) => {
const userPicker = new UserPicker(users, showUserCallback, preChangeUserCallback);
if (initialUserId) {
userPicker.setUserId(initialUserId);
}
await userPicker.render();
return userPicker;
};
@@ -0,0 +1,33 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Define all of the selectors we will be using on the grading interface.
*
* @module mod_forum/local/grades/local/grader/user_picker/selectors
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
regions: {
currentUser: '[data-region="user_picker/current_user"]',
userRegion: '[data-region="user_picker/user"]',
},
actions: {
changeUser: '[data-action="change-user"]',
}
};
@@ -0,0 +1,251 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Full screen window layout.
*
* @module mod_forum/local/layout/fullscreen
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {addIconToContainer} from 'core/loadingicon';
import {addToastRegion} from 'core/toast';
import * as FocusLockManager from 'core/local/aria/focuslock';
/**
* Get the composed layout.
*
* @method
* @param {string} templateName
* @param {object} context
* @returns {LayoutHelper}
*/
export const createLayout = ({
fullscreen = true,
showLoader = false,
focusOnClose = null,
} = {}) => {
const container = document.createElement('div');
document.body.append(container);
container.classList.add('layout');
container.classList.add('fullscreen');
container.setAttribute('role', 'application');
addToastRegion(container);
// Lock scrolling on the document body.
lockBodyScroll();
// Lock tab control.
FocusLockManager.trapFocus(container);
const helpers = getLayoutHelpers(container, FocusLockManager, focusOnClose);
if (showLoader) {
helpers.showLoadingIcon();
}
if (fullscreen) {
helpers.requestFullscreen();
}
return helpers;
};
/**
* LayoutHelper A helper object containing functions for managing the current fullscreen layout
*
* @typedef {object}
* @property {Function} close A function to close the fullscreen layout
* @property {Function} toggleFullscreen A function to toggle the fullscreen from active to disabled and back
* @property {Function} requestFullscreen Make a request to the browser to make the window full screen.
* Note: This must be called in response to a direct user action
* @property {Function} exitFullscreen Exit the fullscreen mode
* @property {Function} getContainer Get the container of the fullscreen layout
* @property {Function} setContent Set the content of the fullscreen layout
* @property {Function} showLoadingIcon Display the loading icon
* @property {Function} hideLoadingIcon Hide the loading icon
*/
/**
* Get the layout helpers.
*
* @method
* @private
* @param {HTMLElement} layoutNode
* @param {FocusLockManager} FocusLockManager
* @param {Boolean} focusOnClose
* @returns {LayoutHelper}
*/
const getLayoutHelpers = (layoutNode, FocusLockManager, focusOnClose) => {
const contentNode = document.createElement('div');
layoutNode.append(contentNode);
const loadingNode = document.createElement('div');
layoutNode.append(loadingNode);
/**
* Close and destroy the window container.
*/
const close = () => {
exitFullscreen();
unlockBodyScroll();
FocusLockManager.untrapFocus();
layoutNode.remove();
if (focusOnClose) {
try {
focusOnClose.focus();
} catch (e) {
// eslint-disable-line
}
}
};
/**
* Attempt to make the conatiner full screen.
*/
const requestFullscreen = () => {
if (layoutNode.requestFullscreen) {
layoutNode.requestFullscreen();
} else if (layoutNode.msRequestFullscreen) {
layoutNode.msRequestFullscreen();
} else if (layoutNode.mozRequestFullscreen) {
layoutNode.mozRequestFullscreen();
} else if (layoutNode.webkitRequestFullscreen) {
layoutNode.webkitRequestFullscreen();
} else {
// Not supported.
// Hack to make this act like full-screen as much as possible.
layoutNode.setTop(0);
}
};
/**
* Exit full screen but do not close the container fully.
*/
const exitFullscreen = () => {
if (document.exitRequestFullScreen) {
if (document.fullScreenElement !== layoutNode) {
return;
}
document.exitRequestFullScreen();
} else if (document.msExitFullscreen) {
if (document.msFullscreenElement !== layoutNode) {
return;
}
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
if (document.mozFullScreenElement !== layoutNode) {
return;
}
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
if (document.webkitFullscreenElement !== layoutNode) {
return;
}
document.webkitExitFullscreen();
}
};
const toggleFullscreen = () => {
if (document.exitRequestFullScreen) {
if (document.fullScreenElement === layoutNode) {
exitFullscreen();
} else {
requestFullscreen();
}
} else if (document.msExitFullscreen) {
if (document.msFullscreenElement === layoutNode) {
exitFullscreen();
} else {
requestFullscreen();
}
} else if (document.mozCancelFullScreen) {
if (document.mozFullScreenElement === layoutNode) {
exitFullscreen();
} else {
requestFullscreen();
}
} else if (document.webkitExitFullscreen) {
if (document.webkitFullscreenElement === layoutNode) {
exitFullscreen();
} else {
requestFullscreen();
}
}
};
/**
* Get the Node which is fullscreen.
*
* @return {Element}
*/
const getContainer = () => {
return contentNode;
};
const setContent = (content) => {
hideLoadingIcon();
// Note: It would be better to use replaceWith, but this is not compatible with IE.
let child = contentNode.lastElementChild;
while (child) {
contentNode.removeChild(child);
child = contentNode.lastElementChild;
}
contentNode.append(content);
};
const showLoadingIcon = () => {
addIconToContainer(loadingNode);
};
const hideLoadingIcon = () => {
// Hide the loading container.
let child = loadingNode.lastElementChild;
while (child) {
loadingNode.removeChild(child);
child = loadingNode.lastElementChild;
}
};
/**
* @return {Object}
*/
return {
close,
toggleFullscreen,
requestFullscreen,
exitFullscreen,
getContainer,
setContent,
showLoadingIcon,
hideLoadingIcon,
};
};
const lockBodyScroll = () => {
document.querySelector('body').classList.add('overflow-hidden');
};
const unlockBodyScroll = () => {
document.querySelector('body').classList.remove('overflow-hidden');
};
+5
View File
@@ -0,0 +1,5 @@
import {createLayout as createFullScreenWindow} from './layout/fullscreen';
export {
createFullScreenWindow
};
+65
View File
@@ -0,0 +1,65 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handle the manual locking of individual discussions
*
* @module mod_forum/lock_toggle
* @copyright 2019 Peter Dias <peter@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/templates',
'core/notification',
'mod_forum/repository',
'mod_forum/selectors',
], function(
$,
Templates,
Notification,
Repository,
Selectors
) {
/**
* Register event listeners for the subscription toggle.
*
* @param {object} root The discussion list root element
* @param {boolean} preventDefault Should the default action of the event be prevented
*/
var registerEventListeners = function(root, preventDefault) {
root.on('click', Selectors.lock.toggle, function(e) {
var toggleElement = $(this);
var forumId = toggleElement.data('forumid');
var discussionId = toggleElement.data('discussionid');
var state = toggleElement.data('state');
Repository.setDiscussionLockState(forumId, discussionId, state)
.then(function() {
return location.reload();
})
.catch(Notification.exception);
if (preventDefault) {
e.preventDefault();
}
});
};
return {
init: registerEventListeners
};
});
+84
View File
@@ -0,0 +1,84 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module mod_forum/pin_toggle
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/ajax',
'core/str',
'core/templates',
'core/notification',
'mod_forum/repository',
'mod_forum/selectors',
'core/str',
], function(
$,
Ajax,
Str,
Templates,
Notification,
Repository,
Selectors,
String
) {
/**
* Registery event listeners for the pin toggle.
*
* @param {object} root The calendar root element
* @param {boolean} preventDefault Should the default action of the event be prevented
* @param {function} callback Success callback
*/
var registerEventListeners = function(root, preventDefault, callback) {
root.on('click', Selectors.pin.toggle, function(e) {
var toggleElement = $(this);
var forumid = toggleElement.data('forumid');
var discussionid = toggleElement.data('discussionid');
var pinstate = toggleElement.data('targetstate');
Repository.setPinDiscussionState(forumid, discussionid, pinstate)
.then(function(context) {
return callback(toggleElement, context);
})
.then(function() {
return String.get_string("pinupdated", "forum")
.done(function(s) {
return Notification.addNotification({
message: s,
type: "info"
});
});
})
.fail(Notification.exception);
if (preventDefault) {
e.preventDefault();
}
});
};
return {
init: registerEventListeners
};
});
+102
View File
@@ -0,0 +1,102 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module mod_forum/posts_list
* @copyright 2019 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/templates',
'core/notification',
'core/pending',
'mod_forum/selectors',
'mod_forum/inpage_reply',
'core_form/changechecker',
], function(
$,
Templates,
Notification,
Pending,
Selectors,
InPageReply,
FormChangeChecker
) {
var registerEventListeners = function(root, throttlingwarningmsg) {
root.on('click', Selectors.post.inpageReplyLink, function(e) {
e.preventDefault();
// After adding a reply a url hash is being generated that scrolls (points) to the newly added reply.
// The hash being present causes this scrolling behavior to the particular reply to persists even when
// another, non-related in-page replay link is being clicked which ultimately causes a bad user experience.
// A particular solution for this problem would be changing the browser's history state when a url hash is
// present.
if (window.location.hash) {
// Remove the fragment identifier from the url.
var url = window.location.href.split('#')[0];
history.pushState({}, document.title, url);
}
var pending = new Pending('inpage-reply');
var currentTarget = $(e.currentTarget).parents(Selectors.post.forumCoreContent);
var currentSubject = currentTarget.find(Selectors.post.forumSubject);
var currentRoot = $(e.currentTarget).parents(Selectors.post.forumContent);
var context = {
postid: $(currentRoot).data('post-id'),
"reply_url": $(e.currentTarget).attr('href'),
sesskey: M.cfg.sesskey,
parentsubject: currentSubject.data('replySubject'),
canreplyprivately: $(e.currentTarget).data('can-reply-privately'),
postformat: InPageReply.CONTENT_FORMATS.MOODLE,
throttlingwarningmsg: throttlingwarningmsg
};
if (!currentRoot.find(Selectors.post.inpageReplyContent).length) {
Templates.render('mod_forum/inpage_reply', context)
.then(function(html, js) {
return Templates.appendNodeContents(currentTarget, html, js);
})
.then(function() {
return currentRoot.find(Selectors.post.inpageReplyContent)
.slideToggle(300, pending.resolve).find('textarea').focus();
})
.then(function() {
FormChangeChecker.watchFormById(`inpage-reply-${context.postid}`);
return;
})
.catch(Notification.exception);
} else {
var form = currentRoot.find(Selectors.post.inpageReplyContent);
form.slideToggle(300, pending.resolve);
if (form.is(':visible')) {
form.find('textarea').focus();
}
}
});
};
return {
init: function(root, throttlingwarningmsg) {
registerEventListeners(root, throttlingwarningmsg);
InPageReply.init(root);
}
};
});
+165
View File
@@ -0,0 +1,165 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Forum repository class to encapsulate all of the AJAX requests that subscribe or unsubscribe
* can be sent for forum.
*
* @module mod_forum/repository
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/ajax'], function(Ajax) {
/**
* Set the subscription state for a discussion in a forum.
*
* @param {number} forumId ID of the forum the discussion belongs to
* @param {number} discussionId ID of the discussion with the subscription state
* @param {boolean} targetState Set the subscribed state. True == subscribed; false == unsubscribed.
* @return {object} jQuery promise
*/
var setDiscussionSubscriptionState = function(forumId, discussionId, targetState) {
var request = {
methodname: 'mod_forum_set_subscription_state',
args: {
forumid: forumId,
discussionid: discussionId,
targetstate: targetState
}
};
return Ajax.call([request])[0];
};
var addDiscussionPost = function(postid, subject, message, messageformat, isprivatereply, topreferredformat) {
var request = {
methodname: 'mod_forum_add_discussion_post',
args: {
postid: postid,
message: message,
messageformat: messageformat,
subject: subject,
options: [{
name: "private",
value: isprivatereply,
}, {
name: "topreferredformat",
value: topreferredformat,
}]
}
};
return Ajax.call([request])[0];
};
/**
* Set the favourite state for a discussion in a forum.
*
* @param {number} forumId ID of the forum the discussion belongs to
* @param {number} discussionId ID of the discussion with the subscription state
* @param {null|date} targetState Set the favourite state. True == favourited; false == unfavourited.
* @return {object} jQuery promise
*/
var setFavouriteDiscussionState = function(forumId, discussionId, targetState) {
var request = {
methodname: 'mod_forum_toggle_favourite_state',
args: {
discussionid: discussionId,
targetstate: targetState
}
};
return Ajax.call([request])[0];
};
var setDiscussionLockState = function(forumId, discussionId, targetState) {
var request = {
methodname: 'mod_forum_set_lock_state',
args: {
forumid: forumId,
discussionid: discussionId,
targetstate: targetState}
};
return Ajax.call([request])[0];
};
/**
* Set the pinned state for the discussion provided.
*
* @param {number} forumid
* @param {number} discussionid
* @param {boolean} targetstate
* @return {*|Promise}
*/
var setPinDiscussionState = function(forumid, discussionid, targetstate) {
var request = {
methodname: 'mod_forum_set_pin_state',
args: {
discussionid: discussionid,
targetstate: targetstate
}
};
return Ajax.call([request])[0];
};
/**
* Get the discussions for the user and cmid provided.
*
* @param {number} userid
* @param {number} cmid
* @param {string} sortby
* @param {string} sortdirection
* @return {*|Promise}
*/
var getDiscussionByUserID = function(userid, cmid, sortby = 'modified', sortdirection = 'DESC') {
var request = {
methodname: 'mod_forum_get_discussion_posts_by_userid',
args: {
userid: userid,
cmid: cmid,
sortby: sortby,
sortdirection: sortdirection,
},
};
return Ajax.call([request])[0];
};
/**
* Get the posts for the discussion ID provided.
*
* @param {number} discussionId
* @param {String} sortby
* @param {String} sortdirection
* @return {*|Promise}
*/
var getDiscussionPosts = function(discussionId, sortby = 'created', sortdirection = 'ASC') {
var request = {
methodname: 'mod_forum_get_discussion_posts',
args: {
discussionid: discussionId,
sortby: sortby,
sortdirection: sortdirection,
},
};
return Ajax.call([request])[0];
};
return {
setDiscussionSubscriptionState: setDiscussionSubscriptionState,
addDiscussionPost: addDiscussionPost,
setDiscussionLockState: setDiscussionLockState,
setFavouriteDiscussionState: setFavouriteDiscussionState,
setPinDiscussionState: setPinDiscussionState,
getDiscussionByUserID: getDiscussionByUserID,
getDiscussionPosts: getDiscussionPosts,
};
});
+75
View File
@@ -0,0 +1,75 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Common CSS selectors for the forum UI.
*
* @module mod_forum/selectors
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
return {
subscription: {
toggle: "[data-type='subscription-toggle'][data-action='toggle']",
},
summary: {
actions: "[data-container='discussion-summary-actions']"
},
post: {
post: '[data-region="post"]',
action: '[data-region="post-action"]',
actionsContainer: '[data-region="post-actions-container"]',
authorName: '[data-region="author-name"]',
forumCoreContent: "[data-region-content='forum-post-core']",
forumContent: "[data-content='forum-post']",
forumSubject: "[data-region-content='forum-post-core-subject']",
inpageCancelButton: "button[name='cancelbtn']",
inpageReplyButton: "button",
inpageReplyLink: "[data-action='collapsible-link']",
inpageReplyCancelButton: "[data-action='cancel-inpage-reply']",
inpageReplyCreateButton: "[data-action='create-inpage-reply']",
inpageReplyContainer: '[data-region="inpage-reply-container"]',
inpageReplyContent: "[data-content='inpage-reply-content']",
inpageReplyForm: "form[data-content='inpage-reply-form']",
inpageSubmitBtn: "[data-action='forum-inpage-submit']",
inpageSubmitBtnText: "[data-region='submit-text']",
loadingIconContainer: "[data-region='loading-icon-container']",
repliesContainer: "[data-region='replies-container']",
replyCount: '[data-region="reply-count"]',
modeSelect: "select[name='mode']",
showReplies: '[data-action="show-replies"]',
hideReplies: '[data-action="hide-replies"]',
repliesVisibilityToggleContainer: '[data-region="replies-visibility-toggle-container"]'
},
lock: {
toggle: "[data-action='toggle'][data-type='lock-toggle']",
icon: "[data-region='locked-icon']"
},
favourite: {
toggle: "[data-type='favorite-toggle'][data-action='toggle']",
},
pin: {
toggle: "[data-type='pin-toggle'][data-action='toggle']",
},
discussion: {
tools: '[data-container="discussion-tools"]',
item: '[data-region="discussion-list-item"]',
lockedLabel: "[data-region='locked-label']",
subscribedLabel: "[data-region='subscribed-label']",
timedLabel: "[data-region='timed-label']",
},
};
});
+75
View File
@@ -0,0 +1,75 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handle discussion subscription toggling on a discussion list in
* the forum view.
*
* @module mod_forum/subscription_toggle
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/templates',
'core/notification',
'mod_forum/repository',
'mod_forum/selectors',
'core/pubsub',
'mod_forum/forum_events',
], function(
$,
Templates,
Notification,
Repository,
Selectors,
PubSub,
ForumEvents
) {
/**
* Register event listeners for the subscription toggle.
*
* @param {object} root The discussion list root element
* @param {boolean} preventDefault Should the default action of the event be prevented
* @param {function} callback Success callback
*/
var registerEventListeners = function(root, preventDefault, callback) {
root.on('click', Selectors.subscription.toggle, function(e) {
var toggleElement = $(this);
var forumId = toggleElement.data('forumid');
var discussionId = toggleElement.data('discussionid');
var subscriptionState = toggleElement.data('targetstate');
Repository.setDiscussionSubscriptionState(forumId, discussionId, subscriptionState)
.then(function(context) {
PubSub.publish(ForumEvents.SUBSCRIPTION_TOGGLED, {
discussionId: discussionId,
subscriptionState: subscriptionState
});
return callback(toggleElement, context);
})
.catch(Notification.exception);
if (preventDefault) {
e.preventDefault();
}
});
};
return {
init: registerEventListeners
};
});
+135
View File
@@ -0,0 +1,135 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides support for the conversion of moodle1 backup to the moodle2 format
*
* @package mod_forum
* @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Forum conversion handler
*/
class moodle1_mod_forum_handler extends moodle1_mod_handler {
/** @var moodle1_file_manager */
protected $fileman = null;
/** @var int cmid */
protected $moduleid = null;
/**
* Declare the paths in moodle.xml we are able to convert
*
* The method returns list of {@link convert_path} instances.
* For each path returned, the corresponding conversion method must be
* defined.
*
* Note that the paths /MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM do not
* actually exist in the file. The last element with the module name was
* appended by the moodle1_converter class.
*
* @return array of {@link convert_path} instances
*/
public function get_paths() {
return array(
new convert_path('forum', '/MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM',
array(
'renamefields' => array(
'format' => 'messageformat',
),
'newfields' => array(
'completiondiscussions' => 0,
'completionreplies' => 0,
'completionpost' => 0,
'maxattachments' => 1,
'introformat' => 0,
),
)
),
);
}
/**
* Converts /MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM data
*/
public function process_forum($data) {
global $CFG;
// get the course module id and context id
$instanceid = $data['id'];
$cminfo = $this->get_cminfo($instanceid);
$this->moduleid = $cminfo['id'];
$contextid = $this->converter->get_contextid(CONTEXT_MODULE, $this->moduleid);
// get a fresh new file manager for this instance
$this->fileman = $this->converter->get_file_manager($contextid, 'mod_forum');
// convert course files embedded into the intro
$this->fileman->filearea = 'intro';
$this->fileman->itemid = 0;
$data['intro'] = moodle1_converter::migrate_referenced_files($data['intro'], $this->fileman);
// Convert the introformat if necessary.
if ($CFG->texteditors !== 'textarea') {
$data['intro'] = text_to_html($data['intro'], false, false, true);
$data['introformat'] = FORMAT_HTML;
}
// start writing forum.xml
$this->open_xml_writer("activities/forum_{$this->moduleid}/forum.xml");
$this->xmlwriter->begin_tag('activity', array('id' => $instanceid, 'moduleid' => $this->moduleid,
'modulename' => 'forum', 'contextid' => $contextid));
$this->xmlwriter->begin_tag('forum', array('id' => $instanceid));
foreach ($data as $field => $value) {
if ($field <> 'id') {
$this->xmlwriter->full_tag($field, $value);
}
}
$this->xmlwriter->begin_tag('discussions');
return $data;
}
/**
* This is executed when we reach the closing </MOD> tag of our 'forum' path
*/
public function on_forum_end() {
// finish writing forum.xml
$this->xmlwriter->end_tag('discussions');
$this->xmlwriter->end_tag('forum');
$this->xmlwriter->end_tag('activity');
$this->close_xml_writer();
// write inforef.xml
$this->open_xml_writer("activities/forum_{$this->moduleid}/inforef.xml");
$this->xmlwriter->begin_tag('inforef');
$this->xmlwriter->begin_tag('fileref');
foreach ($this->fileman->get_fileids() as $fileid) {
$this->write_xml('file', array('id' => $fileid));
}
$this->xmlwriter->end_tag('fileref');
$this->xmlwriter->end_tag('inforef');
$this->close_xml_writer();
}
}
@@ -0,0 +1,87 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines backup_forum_activity_task class
*
* @package mod_forum
* @category backup
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/forum/backup/moodle2/backup_forum_stepslib.php');
require_once($CFG->dirroot . '/mod/forum/backup/moodle2/backup_forum_settingslib.php');
/**
* Provides the steps to perform one complete backup of the Forum instance
*/
class backup_forum_activity_task extends backup_activity_task {
/**
* No specific settings for this activity
*/
protected function define_my_settings() {
}
/**
* Defines a backup step to store the instance data in the forum.xml file
*/
protected function define_my_steps() {
$this->add_step(new backup_forum_activity_structure_step('forum structure', 'forum.xml'));
}
/**
* Encodes URLs to the index.php, view.php and discuss.php scripts
*
* @param string $content some HTML text that eventually contains URLs to the activity instance scripts
* @return string the content with the URLs encoded
*/
public static function encode_content_links($content) {
global $CFG;
$base = preg_quote($CFG->wwwroot,"/");
// Link to the list of forums
$search="/(".$base."\/mod\/forum\/index.php\?id\=)([0-9]+)/";
$content= preg_replace($search, '$@FORUMINDEX*$2@$', $content);
// Link to forum view by moduleid
$search="/(".$base."\/mod\/forum\/view.php\?id\=)([0-9]+)/";
$content= preg_replace($search, '$@FORUMVIEWBYID*$2@$', $content);
// Link to forum view by forumid
$search="/(".$base."\/mod\/forum\/view.php\?f\=)([0-9]+)/";
$content= preg_replace($search, '$@FORUMVIEWBYF*$2@$', $content);
// Link to forum discussion with parent syntax
$search = "/(".$base."\/mod\/forum\/discuss.php\?d\=)([0-9]+)(?:\&amp;|\&)parent\=([0-9]+)/";
$content= preg_replace($search, '$@FORUMDISCUSSIONVIEWPARENT*$2*$3@$', $content);
// Link to forum discussion with relative syntax
$search="/(".$base."\/mod\/forum\/discuss.php\?d\=)([0-9]+)\#([0-9]+)/";
$content= preg_replace($search, '$@FORUMDISCUSSIONVIEWINSIDE*$2*$3@$', $content);
// Link to forum discussion by discussionid
$search="/(".$base."\/mod\/forum\/discuss.php\?d\=)([0-9]+)/";
$content= preg_replace($search, '$@FORUMDISCUSSIONVIEW*$2@$', $content);
return $content;
}
}
@@ -0,0 +1,27 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package mod_forum
* @subpackage backup-moodle2
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// This activity has not particular settings but the inherited from the generic
// backup_activity_task so here there isn't any class definition, like the ones
// existing in /backup/moodle2/backup_settingslib.php (activities section)
@@ -0,0 +1,223 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package mod_forum
* @subpackage backup-moodle2
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Define all the backup steps that will be used by the backup_forum_activity_task
*/
/**
* Define the complete forum structure for backup, with file and id annotations
*/
class backup_forum_activity_structure_step extends backup_activity_structure_step {
protected function define_structure() {
// To know if we are including userinfo
$userinfo = $this->get_setting_value('userinfo');
// Define each element separated
$forum = new backup_nested_element('forum', array('id'), array(
'type', 'name', 'intro', 'introformat', 'duedate', 'cutoffdate',
'assessed', 'assesstimestart', 'assesstimefinish', 'scale',
'maxbytes', 'maxattachments', 'forcesubscribe', 'trackingtype',
'rsstype', 'rssarticles', 'timemodified', 'warnafter',
'blockafter', 'blockperiod', 'completiondiscussions', 'completionreplies',
'completionposts', 'displaywordcount', 'lockdiscussionafter', 'grade_forum'));
$discussions = new backup_nested_element('discussions');
$discussion = new backup_nested_element('discussion', array('id'), array(
'name', 'firstpost', 'userid', 'groupid',
'assessed', 'timemodified', 'usermodified', 'timestart',
'timeend', 'pinned', 'timelocked'));
$posts = new backup_nested_element('posts');
$post = new backup_nested_element('post', array('id'), array(
'parent', 'userid', 'created', 'modified',
'mailed', 'subject', 'message', 'messageformat',
'messagetrust', 'attachment', 'totalscore', 'mailnow', 'privatereplyto'));
$tags = new backup_nested_element('poststags');
$tag = new backup_nested_element('tag', array('id'), array('itemid', 'rawname'));
$ratings = new backup_nested_element('ratings');
$rating = new backup_nested_element('rating', array('id'), array(
'component', 'ratingarea', 'scaleid', 'value', 'userid', 'timecreated', 'timemodified'));
$discussionsubs = new backup_nested_element('discussion_subs');
$discussionsub = new backup_nested_element('discussion_sub', array('id'), array(
'userid',
'preference',
));
$subscriptions = new backup_nested_element('subscriptions');
$subscription = new backup_nested_element('subscription', array('id'), array(
'userid'));
$digests = new backup_nested_element('digests');
$digest = new backup_nested_element('digest', array('id'), array(
'userid', 'maildigest'));
$readposts = new backup_nested_element('readposts');
$read = new backup_nested_element('read', array('id'), array(
'userid', 'discussionid', 'postid', 'firstread',
'lastread'));
$trackedprefs = new backup_nested_element('trackedprefs');
$track = new backup_nested_element('track', array('id'), array(
'userid'));
$grades = new backup_nested_element('grades');
$grade = new backup_nested_element('grade', ['id'], [
'forum',
'itemnumber',
'userid',
'grade',
'timecreated',
'timemodified',
]);
// Build the tree
$forum->add_child($discussions);
$discussions->add_child($discussion);
$forum->add_child($subscriptions);
$subscriptions->add_child($subscription);
$forum->add_child($digests);
$digests->add_child($digest);
$forum->add_child($readposts);
$readposts->add_child($read);
$forum->add_child($trackedprefs);
$trackedprefs->add_child($track);
$forum->add_child($tags);
$tags->add_child($tag);
$forum->add_child($grades);
$grades->add_child($grade);
$discussion->add_child($posts);
$posts->add_child($post);
$post->add_child($ratings);
$ratings->add_child($rating);
$discussion->add_child($discussionsubs);
$discussionsubs->add_child($discussionsub);
// Define sources
$forum->set_source_table('forum', array('id' => backup::VAR_ACTIVITYID));
// All these source definitions only happen if we are including user info
if ($userinfo) {
$discussion->set_source_sql('
SELECT *
FROM {forum_discussions}
WHERE forum = ?',
array(backup::VAR_PARENTID));
// Need posts ordered by id so parents are always before childs on restore
$post->set_source_table('forum_posts', array('discussion' => backup::VAR_PARENTID), 'id ASC');
$discussionsub->set_source_table('forum_discussion_subs', array('discussion' => backup::VAR_PARENTID));
$subscription->set_source_table('forum_subscriptions', array('forum' => backup::VAR_PARENTID));
$digest->set_source_table('forum_digests', array('forum' => backup::VAR_PARENTID));
$read->set_source_table('forum_read', array('forumid' => backup::VAR_PARENTID));
$track->set_source_table('forum_track_prefs', array('forumid' => backup::VAR_PARENTID));
$rating->set_source_table('rating', array('contextid' => backup::VAR_CONTEXTID,
'component' => backup_helper::is_sqlparam('mod_forum'),
'ratingarea' => backup_helper::is_sqlparam('post'),
'itemid' => backup::VAR_PARENTID));
$rating->set_source_alias('rating', 'value');
if (core_tag_tag::is_enabled('mod_forum', 'forum_posts')) {
// Backup all tags for all forum posts in this forum.
$tag->set_source_sql('SELECT t.id, ti.itemid, t.rawname
FROM {tag} t
JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.itemtype = ?
AND ti.component = ?
AND ti.contextid = ?', array(
backup_helper::is_sqlparam('forum_posts'),
backup_helper::is_sqlparam('mod_forum'),
backup::VAR_CONTEXTID));
}
$grade->set_source_table('forum_grades', array('forum' => backup::VAR_PARENTID));
}
// Define id annotations
$forum->annotate_ids('scale', 'scale');
$discussion->annotate_ids('group', 'groupid');
$post->annotate_ids('user', 'userid');
$discussionsub->annotate_ids('user', 'userid');
$rating->annotate_ids('scale', 'scaleid');
$rating->annotate_ids('user', 'userid');
$subscription->annotate_ids('user', 'userid');
$digest->annotate_ids('user', 'userid');
$read->annotate_ids('user', 'userid');
$track->annotate_ids('user', 'userid');
$grade->annotate_ids('userid', 'userid');
$grade->annotate_ids('forum', 'forum');
// Define file annotations
$forum->annotate_files('mod_forum', 'intro', null); // This file area hasn't itemid
$post->annotate_files('mod_forum', 'post', 'id');
$post->annotate_files('mod_forum', 'attachment', 'id');
// Return the root element (forum), wrapped into standard activity structure
return $this->prepare_activity_structure($forum);
}
}
@@ -0,0 +1,143 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package mod_forum
* @subpackage backup-moodle2
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/forum/backup/moodle2/restore_forum_stepslib.php'); // Because it exists (must)
/**
* forum restore task that provides all the settings and steps to perform one
* complete restore of the activity
*/
class restore_forum_activity_task extends restore_activity_task {
/**
* Define (add) particular settings this activity can have
*/
protected function define_my_settings() {
// No particular settings for this activity
}
/**
* Define (add) particular steps this activity can have
*/
protected function define_my_steps() {
// Choice only has one structure step
$this->add_step(new restore_forum_activity_structure_step('forum_structure', 'forum.xml'));
}
/**
* Define the contents in the activity that must be
* processed by the link decoder
*/
public static function define_decode_contents() {
$contents = array();
$contents[] = new restore_decode_content('forum', array('intro'), 'forum');
$contents[] = new restore_decode_content('forum_posts', array('message'), 'forum_post');
return $contents;
}
/**
* Define the decoding rules for links belonging
* to the activity to be executed by the link decoder
*/
public static function define_decode_rules() {
$rules = array();
// List of forums in course
$rules[] = new restore_decode_rule('FORUMINDEX', '/mod/forum/index.php?id=$1', 'course');
// Forum by cm->id and forum->id
$rules[] = new restore_decode_rule('FORUMVIEWBYID', '/mod/forum/view.php?id=$1', 'course_module');
$rules[] = new restore_decode_rule('FORUMVIEWBYF', '/mod/forum/view.php?f=$1', 'forum');
// Link to forum discussion
$rules[] = new restore_decode_rule('FORUMDISCUSSIONVIEW', '/mod/forum/discuss.php?d=$1', 'forum_discussion');
// Link to discussion with parent and with anchor posts
$rules[] = new restore_decode_rule('FORUMDISCUSSIONVIEWPARENT', '/mod/forum/discuss.php?d=$1&parent=$2',
array('forum_discussion', 'forum_post'));
$rules[] = new restore_decode_rule('FORUMDISCUSSIONVIEWINSIDE', '/mod/forum/discuss.php?d=$1#$2',
array('forum_discussion', 'forum_post'));
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* forum logs. It must return one array
* of {@link restore_log_rule} objects
*/
public static function define_restore_log_rules() {
$rules = array();
$rules[] = new restore_log_rule('forum', 'add', 'view.php?id={course_module}', '{forum}');
$rules[] = new restore_log_rule('forum', 'update', 'view.php?id={course_module}', '{forum}');
$rules[] = new restore_log_rule('forum', 'view', 'view.php?id={course_module}', '{forum}');
$rules[] = new restore_log_rule('forum', 'view forum', 'view.php?id={course_module}', '{forum}');
$rules[] = new restore_log_rule('forum', 'mark read', 'view.php?f={forum}', '{forum}');
$rules[] = new restore_log_rule('forum', 'start tracking', 'view.php?f={forum}', '{forum}');
$rules[] = new restore_log_rule('forum', 'stop tracking', 'view.php?f={forum}', '{forum}');
$rules[] = new restore_log_rule('forum', 'subscribe', 'view.php?f={forum}', '{forum}');
$rules[] = new restore_log_rule('forum', 'unsubscribe', 'view.php?f={forum}', '{forum}');
$rules[] = new restore_log_rule('forum', 'subscriber', 'subscribers.php?id={forum}', '{forum}');
$rules[] = new restore_log_rule('forum', 'subscribers', 'subscribers.php?id={forum}', '{forum}');
$rules[] = new restore_log_rule('forum', 'view subscribers', 'subscribers.php?id={forum}', '{forum}');
$rules[] = new restore_log_rule('forum', 'add discussion', 'discuss.php?d={forum_discussion}', '{forum_discussion}');
$rules[] = new restore_log_rule('forum', 'view discussion', 'discuss.php?d={forum_discussion}', '{forum_discussion}');
$rules[] = new restore_log_rule('forum', 'move discussion', 'discuss.php?d={forum_discussion}', '{forum_discussion}');
$rules[] = new restore_log_rule('forum', 'delete discussi', 'view.php?id={course_module}', '{forum}',
null, 'delete discussion');
$rules[] = new restore_log_rule('forum', 'delete discussion', 'view.php?id={course_module}', '{forum}');
$rules[] = new restore_log_rule('forum', 'add post', 'discuss.php?d={forum_discussion}&parent={forum_post}', '{forum_post}');
$rules[] = new restore_log_rule('forum', 'update post', 'discuss.php?d={forum_discussion}#p{forum_post}&parent={forum_post}', '{forum_post}');
$rules[] = new restore_log_rule('forum', 'update post', 'discuss.php?d={forum_discussion}&parent={forum_post}', '{forum_post}');
$rules[] = new restore_log_rule('forum', 'prune post', 'discuss.php?d={forum_discussion}', '{forum_post}');
$rules[] = new restore_log_rule('forum', 'delete post', 'discuss.php?d={forum_discussion}', '[post]');
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* course logs. It must return one array
* of {@link restore_log_rule} objects
*
* Note this rules are applied when restoring course logs
* by the restore final task, but are defined here at
* activity level. All them are rules not linked to any module instance (cmid = 0)
*/
public static function define_restore_log_rules_for_course() {
$rules = array();
$rules[] = new restore_log_rule('forum', 'view forums', 'index.php?id={course}', null);
$rules[] = new restore_log_rule('forum', 'subscribeall', 'index.php?id={course}', '{course}');
$rules[] = new restore_log_rule('forum', 'unsubscribeall', 'index.php?id={course}', '{course}');
$rules[] = new restore_log_rule('forum', 'user report', 'user.php?course={course}&id={user}&mode=[mode]', '{user}');
$rules[] = new restore_log_rule('forum', 'search', 'search.php?id={course}&search=[searchenc]', '[search]');
return $rules;
}
}
@@ -0,0 +1,307 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package mod_forum
* @subpackage backup-moodle2
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Define all the restore steps that will be used by the restore_forum_activity_task
*/
/**
* Structure step to restore one forum activity
*/
class restore_forum_activity_structure_step extends restore_activity_structure_step {
protected function define_structure() {
$paths = array();
$userinfo = $this->get_setting_value('userinfo');
$paths[] = new restore_path_element('forum', '/activity/forum');
if ($userinfo) {
$paths[] = new restore_path_element('forum_discussion', '/activity/forum/discussions/discussion');
$paths[] = new restore_path_element('forum_post', '/activity/forum/discussions/discussion/posts/post');
$paths[] = new restore_path_element('forum_tag', '/activity/forum/poststags/tag');
$paths[] = new restore_path_element('forum_discussion_sub', '/activity/forum/discussions/discussion/discussion_subs/discussion_sub');
$paths[] = new restore_path_element('forum_rating', '/activity/forum/discussions/discussion/posts/post/ratings/rating');
$paths[] = new restore_path_element('forum_subscription', '/activity/forum/subscriptions/subscription');
$paths[] = new restore_path_element('forum_digest', '/activity/forum/digests/digest');
$paths[] = new restore_path_element('forum_read', '/activity/forum/readposts/read');
$paths[] = new restore_path_element('forum_track', '/activity/forum/trackedprefs/track');
$paths[] = new restore_path_element('forum_grade', '/activity/forum/grades/grade');
}
// Return the paths wrapped into standard activity structure
return $this->prepare_activity_structure($paths);
}
protected function process_forum($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->course = $this->get_courseid();
// Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
// See MDL-9367.
if (!isset($data->duedate)) {
$data->duedate = 0;
}
$data->duedate = $this->apply_date_offset($data->duedate);
if (!isset($data->cutoffdate)) {
$data->cutoffdate = 0;
}
$data->cutoffdate = $this->apply_date_offset($data->cutoffdate);
$data->assesstimestart = $this->apply_date_offset($data->assesstimestart);
$data->assesstimefinish = $this->apply_date_offset($data->assesstimefinish);
if ($data->scale < 0) { // scale found, get mapping
$data->scale = -($this->get_mappingid('scale', abs($data->scale)));
}
$newitemid = $DB->insert_record('forum', $data);
$this->apply_activity_instance($newitemid);
// Add current enrolled user subscriptions if necessary.
$data->id = $newitemid;
$ctx = context_module::instance($this->task->get_moduleid());
forum_instance_created($ctx, $data);
}
protected function process_forum_discussion($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->course = $this->get_courseid();
$data->forum = $this->get_new_parentid('forum');
$data->timestart = $this->apply_date_offset($data->timestart);
$data->timeend = $this->apply_date_offset($data->timeend);
$data->userid = $this->get_mappingid('user', $data->userid);
$data->groupid = $this->get_mappingid('group', $data->groupid);
$data->usermodified = $this->get_mappingid('user', $data->usermodified);
$newitemid = $DB->insert_record('forum_discussions', $data);
$this->set_mapping('forum_discussion', $oldid, $newitemid);
}
protected function process_forum_post($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->discussion = $this->get_new_parentid('forum_discussion');
$data->userid = $this->get_mappingid('user', $data->userid);
// If post has parent, map it (it has been already restored)
if (!empty($data->parent)) {
$data->parent = $this->get_mappingid('forum_post', $data->parent);
}
\mod_forum\local\entities\post::add_message_counts($data);
$newitemid = $DB->insert_record('forum_posts', $data);
$this->set_mapping('forum_post', $oldid, $newitemid, true);
// If !post->parent, it's the 1st post. Set it in discussion
if (empty($data->parent)) {
$DB->set_field('forum_discussions', 'firstpost', $newitemid, array('id' => $data->discussion));
}
}
protected function process_forum_tag($data) {
$data = (object)$data;
if (!core_tag_tag::is_enabled('mod_forum', 'forum_posts')) { // Tags disabled in server, nothing to process.
return;
}
$tag = $data->rawname;
if (!$itemid = $this->get_mappingid('forum_post', $data->itemid)) {
// Some orphaned tag, we could not find the restored post for it - ignore.
return;
}
$context = context_module::instance($this->task->get_moduleid());
core_tag_tag::add_item_tag('mod_forum', 'forum_posts', $itemid, $context, $tag);
}
protected function process_forum_rating($data) {
global $DB;
$data = (object)$data;
// Cannot use ratings API, cause, it's missing the ability to specify times (modified/created)
$data->contextid = $this->task->get_contextid();
$data->itemid = $this->get_new_parentid('forum_post');
if ($data->scaleid < 0) { // scale found, get mapping
$data->scaleid = -($this->get_mappingid('scale', abs($data->scaleid)));
}
$data->rating = $data->value;
$data->userid = $this->get_mappingid('user', $data->userid);
// We need to check that component and ratingarea are both set here.
if (empty($data->component)) {
$data->component = 'mod_forum';
}
if (empty($data->ratingarea)) {
$data->ratingarea = 'post';
}
$newitemid = $DB->insert_record('rating', $data);
}
protected function process_forum_subscription($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->forum = $this->get_new_parentid('forum');
$data->userid = $this->get_mappingid('user', $data->userid);
// Create only a new subscription if it does not already exist (see MDL-59854).
if ($subscription = $DB->get_record('forum_subscriptions',
array('forum' => $data->forum, 'userid' => $data->userid))) {
$this->set_mapping('forum_subscription', $oldid, $subscription->id, true);
} else {
$newitemid = $DB->insert_record('forum_subscriptions', $data);
$this->set_mapping('forum_subscription', $oldid, $newitemid, true);
}
}
protected function process_forum_discussion_sub($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->discussion = $this->get_new_parentid('forum_discussion');
$data->forum = $this->get_new_parentid('forum');
$data->userid = $this->get_mappingid('user', $data->userid);
$newitemid = $DB->insert_record('forum_discussion_subs', $data);
$this->set_mapping('forum_discussion_sub', $oldid, $newitemid, true);
}
protected function process_forum_digest($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->forum = $this->get_new_parentid('forum');
$data->userid = $this->get_mappingid('user', $data->userid);
$newitemid = $DB->insert_record('forum_digests', $data);
}
protected function process_forum_grade($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->forum = $this->get_new_parentid('forum');
$data->userid = $this->get_mappingid('user', $data->userid);
// We want to ensure the current user has an ID that we can associate to a grade.
if ($data->userid != 0) {
$newitemid = $DB->insert_record('forum_grades', $data);
// Note - the old contextid is required in order to be able to restore files stored in
// sub plugin file areas attached to the gradeid.
$this->set_mapping('grade', $oldid, $newitemid, false, null, $this->task->get_old_contextid());
$this->set_mapping(restore_gradingform_plugin::itemid_mapping('forum'), $oldid, $newitemid);
}
}
protected function process_forum_read($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->forumid = $this->get_new_parentid('forum');
$data->discussionid = $this->get_mappingid('forum_discussion', $data->discussionid);
$data->postid = $this->get_mappingid('forum_post', $data->postid);
$data->userid = $this->get_mappingid('user', $data->userid);
$newitemid = $DB->insert_record('forum_read', $data);
}
protected function process_forum_track($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->forumid = $this->get_new_parentid('forum');
$data->userid = $this->get_mappingid('user', $data->userid);
$newitemid = $DB->insert_record('forum_track_prefs', $data);
}
protected function after_execute() {
// Add forum related files, no need to match by itemname (just internally handled context)
$this->add_related_files('mod_forum', 'intro', null);
// Add post related files, matching by itemname = 'forum_post'
$this->add_related_files('mod_forum', 'post', 'forum_post');
$this->add_related_files('mod_forum', 'attachment', 'forum_post');
}
protected function after_restore() {
global $DB;
// If the forum is of type 'single' and no discussion has been ignited
// (non-userinfo backup/restore) create the discussion here, using forum
// information as base for the initial post.
$forumid = $this->task->get_activityid();
$forumrec = $DB->get_record('forum', array('id' => $forumid));
if ($forumrec->type == 'single' && !$DB->record_exists('forum_discussions', array('forum' => $forumid))) {
// Create single discussion/lead post from forum data
$sd = new stdClass();
$sd->course = $forumrec->course;
$sd->forum = $forumrec->id;
$sd->name = $forumrec->name;
$sd->assessed = $forumrec->assessed;
$sd->message = $forumrec->intro;
$sd->messageformat = $forumrec->introformat;
$sd->messagetrust = true;
$sd->mailnow = false;
$sdid = forum_add_discussion($sd, null, null, $this->task->get_userid());
// Mark the post as mailed
$DB->set_field ('forum_posts','mailed', '1', array('discussion' => $sdid));
// Copy all the files from mod_foum/intro to mod_forum/post
$fs = get_file_storage();
$files = $fs->get_area_files($this->task->get_contextid(), 'mod_forum', 'intro');
foreach ($files as $file) {
$newfilerecord = new stdClass();
$newfilerecord->filearea = 'post';
$newfilerecord->itemid = $DB->get_field('forum_discussions', 'firstpost', array('id' => $sdid));
$fs->create_file_from_storedfile($newfilerecord, $file);
}
}
}
}
@@ -0,0 +1,93 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Activity base class.
*
* @package mod_forum
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Activity base class.
*
* @package mod_forum
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
/**
* feedback_viewed_events
*
* @return string[]
*/
protected function feedback_viewed_events() {
// We could add any forum event, but it will make feedback_post_action slower.
return array('\mod_forum\event\assessable_uploaded', '\mod_forum\event\course_module_viewed',
'\mod_forum\event\discussion_viewed');
}
/**
* feedback_post_action
*
* @param \cm_info $cm
* @param int $contextid
* @param int $userid
* @param string[] $eventnames
* @param int $after
* @return bool
*/
protected function feedback_post_action(\cm_info $cm, $contextid, $userid, $eventnames, $after = false) {
if (empty($this->activitylogs[$contextid][$userid])) {
return false;
}
$logs = $this->activitylogs[$contextid][$userid];
if (empty($logs['\mod_forum\event\assessable_uploaded'])) {
// No feedback viewed if there is no submission.
return false;
}
// First user post time.
$firstpost = $logs['\mod_forum\event\assessable_uploaded']->timecreated[0];
// We consider feedback any other user post in any of this forum discussions.
foreach ($this->activitylogs[$contextid] as $anotheruserid => $logs) {
if ($anotheruserid == $userid) {
continue;
}
if (empty($logs['\mod_forum\event\assessable_uploaded'])) {
continue;
}
$firstpostsenttime = $logs['\mod_forum\event\assessable_uploaded']->timecreated[0];
if (parent::feedback_post_action($cm, $contextid, $userid, $eventnames, $firstpostsenttime)) {
return true;
}
// Continue with the next user.
}
return false;
}
}
@@ -0,0 +1,75 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Cognitive depth indicator - forum.
*
* @package mod_forum
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Cognitive depth indicator - forum.
*
* @package mod_forum
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cognitive_depth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:cognitivedepth', 'mod_forum');
}
public function get_indicator_type() {
return self::INDICATOR_COGNITIVE;
}
public function get_cognitive_depth_level(\cm_info $cm) {
return self::COGNITIVE_LEVEL_4;
}
/**
* feedback_check_grades
*
* @return bool
*/
protected function feedback_check_grades() {
return false;
}
/**
* feedback_replied_events
*
* @return string[]
*/
protected function feedback_replied_events() {
return array('\mod_forum\event\assessable_uploaded');
}
}
@@ -0,0 +1,56 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Social breadth indicator - forum.
*
* @package mod_forum
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Social breadth indicator - forum.
*
* @package mod_forum
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class social_breadth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:socialbreadth', 'mod_forum');
}
public function get_indicator_type() {
return self::INDICATOR_SOCIAL;
}
public function get_social_breadth_level(\cm_info $cm) {
return self::SOCIAL_LEVEL_2;
}
}
@@ -0,0 +1,118 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace mod_forum\completion;
use core_completion\activity_custom_completion;
/**
* Activity custom completion subclass for the forum activity.
*
* Class for defining mod_forum's custom completion rules and fetching the completion statuses
* of the custom completion rules for a given forum instance and a user.
*
* @package mod_forum
* @copyright Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_completion extends activity_custom_completion {
/**
* Fetches the completion state for a given completion rule.
*
* @param string $rule The completion rule.
* @return int The completion state.
*/
public function get_state(string $rule): int {
global $DB;
$this->validate_rule($rule);
$userid = $this->userid;
$forumid = $this->cm->instance;
if (!$forum = $DB->get_record('forum', ['id' => $forumid])) {
throw new \moodle_exception('Unable to find forum with id ' . $forumid);
}
$postcountparams = ['userid' => $userid, 'forumid' => $forumid];
$postcountsql = "SELECT COUNT(*)
FROM {forum_posts} fp
JOIN {forum_discussions} fd ON fp.discussion = fd.id
WHERE fp.userid = :userid
AND fd.forum = :forumid";
if ($rule == 'completiondiscussions') {
$status = $forum->completiondiscussions <=
$DB->count_records('forum_discussions', ['forum' => $forumid, 'userid' => $userid]);
} else if ($rule == 'completionreplies') {
$status = $forum->completionreplies <=
$DB->get_field_sql($postcountsql . ' AND fp.parent <> 0', $postcountparams);
} else if ($rule == 'completionposts') {
$status = $forum->completionposts <= $DB->get_field_sql($postcountsql, $postcountparams);
}
return $status ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE;
}
/**
* Fetch the list of custom completion rules that this module defines.
*
* @return array
*/
public static function get_defined_custom_rules(): array {
return [
'completiondiscussions',
'completionreplies',
'completionposts',
];
}
/**
* Returns an associative array of the descriptions of custom completion rules.
*
* @return array
*/
public function get_custom_rule_descriptions(): array {
$completiondiscussions = $this->cm->customdata['customcompletionrules']['completiondiscussions'] ?? 0;
$completionreplies = $this->cm->customdata['customcompletionrules']['completionreplies'] ?? 0;
$completionposts = $this->cm->customdata['customcompletionrules']['completionposts'] ?? 0;
return [
'completiondiscussions' => get_string('completiondetail:discussions', 'forum', $completiondiscussions),
'completionreplies' => get_string('completiondetail:replies', 'forum', $completionreplies),
'completionposts' => get_string('completiondetail:posts', 'forum', $completionposts),
];
}
/**
* Returns an array of all completion rules, in the order they should be displayed to users.
*
* @return array
*/
public function get_sort_order(): array {
return [
'completionview',
'completiondiscussions',
'completionreplies',
'completionposts',
'completionusegrade',
'completionpassgrade',
];
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the class for fetching the important dates in mod_forum for a given module instance and a user.
*
* @package mod_forum
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace mod_forum;
use core\activity_dates;
/**
* Class for fetching the important dates in mod_forum for a given module instance and a user.
*
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dates extends activity_dates {
/**
* Returns a list of important dates in mod_forum
*
* @return array
*/
protected function get_dates(): array {
$duedate = $this->cm->customdata['duedate'] ?? null;
$dates = [];
if ($duedate) {
$dates[] = [
'dataid' => 'duedate',
'label' => get_string('activitydate:due', 'mod_forum'),
'timestamp' => (int) $duedate,
];
}
return $dates;
}
}
@@ -0,0 +1,111 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum assessable uploaded event.
*
* @package mod_forum
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum assessable uploaded event class.
*
* @property-read array $other {
* Extra information about event.
*
* - int discussionid: id of discussion.
* - string triggeredfrom: name of the function from where event was triggered.
* }
*
* @package mod_forum
* @since Moodle 2.6
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assessable_uploaded extends \core\event\assessable_uploaded {
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has posted content in the forum post with id '$this->objectid' " .
"in the discussion '{$this->other['discussionid']}' located in the forum with course module id " .
"'$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventassessableuploaded', 'mod_forum');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/discuss.php', array('d' => $this->other['discussionid'], 'parent' => $this->objectid));
}
/**
* Init method.
*
* @return void
*/
protected function init() {
parent::init();
$this->data['objecttable'] = 'forum_posts';
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['discussionid'])) {
throw new \coding_exception('The \'discussionid\' value must be set in other.');
} else if (!isset($this->other['triggeredfrom'])) {
throw new \coding_exception('The \'triggeredfrom\' value must be set in other.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_posts', 'restore' => 'forum_post');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['discussionid'] = array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
return $othermapped;
}
}
@@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum instance list viewed event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum instance list viewed event class.
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
// No need for any code here as everything is handled by the parent class.
}
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum course module viewed event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum course module viewed event class.
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_viewed extends \core\event\course_module_viewed {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'forum';
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/view.php', array('f' => $this->objectid));
}
public static function get_objectid_mapping() {
return array('db' => 'forum', 'restore' => 'forum');
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum course searched event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum course searched event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - string searchterm: The searchterm used on forum search.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_searched extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
$searchterm = s($this->other['searchterm']);
return "The user with id '$this->userid' has searched the course with id '$this->courseid' for forum posts " .
"containing \"{$searchterm}\".";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventcoursesearched', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/search.php',
array('id' => $this->courseid, 'search' => $this->other['searchterm']));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['searchterm'])) {
throw new \coding_exception('The \'searchterm\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_COURSE) {
throw new \coding_exception('Context level must be CONTEXT_COURSE.');
}
}
public static function get_other_mapping() {
return false;
}
}
@@ -0,0 +1,110 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum discussion created event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum discussion created event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int forumid: The id of the forum the discussion is in.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_created extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'forum_discussions';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has created the discussion with id '$this->objectid' in the forum " .
"with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdiscussioncreated', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/discuss.php', array('d' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
return $othermapped;
}
}
@@ -0,0 +1,112 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum discussion deleted event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum discussion deleted event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int forumid: The id of the forum the discussion is in.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_deleted extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'forum_discussions';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has deleted the discussion with id '$this->objectid' in the forum " .
"with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdiscussiondeleted', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/view.php', array('id' => $this->contextinstanceid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
return $othermapped;
}
}
@@ -0,0 +1,111 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_forum\event;
use coding_exception;
use moodle_url;
/**
* The mod_forum discussion lock updated event.
*
* @package mod_forum
* @copyright 2022 Université Rennes 2 {@link https://www.univ-rennes2.fr}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_lock_updated extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init(): void {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'forum_discussions';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description(): string {
return "The user with id '$this->userid' {$this->other['status']} the discussion: $this->objectid".
" in the forum: {$this->other['forumid']}";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name(): string {
return get_string('eventdiscussionlockupdated', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/mod/forum/discuss.php', ['d' => $this->objectid]);
}
/**
* Custom validation.
*
* @throws coding_exception
* @return void
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->other['forumid'])) {
throw new coding_exception('forumid must be set in $other.');
}
if (!isset($this->other['status'])) {
throw new \coding_exception('The \'status\' value must be set in other.');
}
if (!in_array($this->other['status'], ['locked', 'unlocked'], true)) {
throw new \coding_exception('The \'status\' value must be \'locked\' or \'unlocked\'.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new coding_exception('Context passed must be module context.');
}
if (!isset($this->objectid)) {
throw new coding_exception('objectid must be set to the discussionid.');
}
}
/**
* Forum discussion object id mappings.
*
* @return array
*/
public static function get_objectid_mapping(): array {
return ['db' => 'forum_discussions', 'restore' => 'forum_discussion'];
}
/**
* Forum id mappings.
*
* @return array
*/
public static function get_other_mapping(): array {
return ['forumid' => ['db' => 'forum', 'restore' => 'forum']];
}
}
@@ -0,0 +1,116 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum discussion moved event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum discussion moved event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int fromforumid: The id of the forum the discussion is being moved from.
* - int toforumid: The id of the forum the discussion is being moved to.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_moved extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'forum_discussions';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has moved the discussion with id '$this->objectid' from the " .
"forum with id '{$this->other['fromforumid']}' to the forum with id '{$this->other['toforumid']}'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdiscussionmoved', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/discuss.php', array('d' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['fromforumid'])) {
throw new \coding_exception('The \'fromforumid\' value must be set in other.');
}
if (!isset($this->other['toforumid'])) {
throw new \coding_exception('The \'toforumid\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['fromforumid'] = array('db' => 'forum', 'restore' => 'forum');
$othermapped['toforumid'] = array('db' => 'forum', 'restore' => 'forum');
return $othermapped;
}
}
@@ -0,0 +1,112 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum discussion pinned event.
*
* @package mod_forum
* @copyright 2014 Charles Fulton <fultonc@lafayette.edu>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum discussion pinned event.
*
* @package mod_forum
* @copyright 2014 Charles Fulton <fultonc@lafayette.edu>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_pinned extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'forum_discussions';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user {$this->userid} has pinned the discussion {$this->objectid} in the forum {$this->other['forumid']}";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdiscussionpinned', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/discuss.php', array('d' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['forumid'])) {
throw new \coding_exception('forumid must be set in $other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context passed must be module context.');
}
if (!isset($this->objectid)) {
throw new \coding_exception('objectid must be set to the discussionid.');
}
}
/**
* Forum discussion object id mappings.
*
* @return array
*/
public static function get_objectid_mapping() {
return array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
}
/**
* Forum id mappings.
*
* @return array
*/
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
return $othermapped;
}
}
@@ -0,0 +1,124 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum discussion_subscription created event.
*
* @package mod_forum
* @copyright 2014 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum discussion_subscription created event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int forumid: The id of the forum which the discussion is in.
* - int discussion: The id of the discussion which has been subscribed to.
* }
*
* @package mod_forum
* @since Moodle 2.8
* @copyright 2014 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_subscription_created extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'forum_discussion_subs';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' subscribed the user with id '$this->relateduserid' to the discussion " .
" with id '{$this->other['discussion']}' in the forum with the course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdiscussionsubscriptioncreated', 'mod_forum');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/subscribe.php', array(
'id' => $this->other['forumid'],
'd' => $this->other['discussion'],
));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if (!isset($this->other['discussion'])) {
throw new \coding_exception('The \'discussion\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_discussion_subs', 'restore' => 'forum_discussion_sub');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
$othermapped['discussion'] = array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
return $othermapped;
}
}
@@ -0,0 +1,124 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum discussion_subscription deleted event.
*
* @package mod_forum
* @copyright 2014 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum discussion_subscription deleted event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int forumid: The id of the forum which the discussion is in.
* - int discussion: The id of the discussion which has been unsubscribed from.
* }
*
* @package mod_forum
* @since Moodle 2.8
* @copyright 2014 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_subscription_deleted extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'forum_discussion_subs';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' unsubscribed the user with id '$this->relateduserid' from the discussion " .
" with id '{$this->other['discussion']}' in the forum with the course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdiscussionsubscriptiondeleted', 'mod_forum');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/subscribe.php', array(
'id' => $this->other['forumid'],
'd' => $this->other['discussion'],
));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if (!isset($this->other['discussion'])) {
throw new \coding_exception('The \'discussion\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_discussion_subs', 'restore' => 'forum_discussion_sub');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
$othermapped['discussion'] = array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
return $othermapped;
}
}
@@ -0,0 +1,112 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum discussion unpinned event.
*
* @package mod_forum
* @copyright 2014 Charles Fulton <fultonc@lafayette.edu>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum discussion unpinned event.
*
* @package mod_forum
* @copyright 2014 Charles Fulton <fultonc@lafayette.edu>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_unpinned extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'forum_discussions';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user {$this->userid} has unpinned the discussion {$this->objectid} in the forum {$this->other['forumid']}";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdiscussionunpinned', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/discuss.php', array('d' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['forumid'])) {
throw new \coding_exception('forumid must be set in $other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context passed must be module context.');
}
if (!isset($this->objectid)) {
throw new \coding_exception('objectid must be set to the discussionid.');
}
}
/**
* Forum discussion object id mappings.
*
* @return array
*/
public static function get_objectid_mapping() {
return array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
}
/**
* Forum id mappings.
*
* @return array
*/
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
return $othermapped;
}
}
@@ -0,0 +1,111 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum discussion updated event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum discussion updated event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int forumid: The id of the forum the discussion is in
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_updated extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'forum_discussions';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has updated the discussion with id '$this->objectid' in the forum " .
"with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdiscussionupdated', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/discuss.php', array('d' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
return $othermapped;
}
}
@@ -0,0 +1,96 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum discussion viewed event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum discussion viewed event class.
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class discussion_viewed extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'forum_discussions';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has viewed the discussion with id '$this->objectid' in the forum " .
"with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventdiscussionviewed', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/discuss.php', array('d' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
}
}
+131
View File
@@ -0,0 +1,131 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum post created event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum post created event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int discussionid: The discussion id the post is part of.
* - int forumid: The forum id the post is part of.
* - string forumtype: The type of forum the post is part of.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class post_created extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'forum_posts';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has created the post with id '$this->objectid' in the discussion with " .
"id '{$this->other['discussionid']}' in the forum with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventpostcreated', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
if ($this->other['forumtype'] == 'single') {
// Single discussion forums are an exception. We show
// the forum itself since it only has one discussion
// thread.
$url = new \moodle_url('/mod/forum/view.php', array('f' => $this->other['forumid']));
} else {
$url = new \moodle_url('/mod/forum/discuss.php', array('d' => $this->other['discussionid']));
}
$url->set_anchor('p'.$this->objectid);
return $url;
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['discussionid'])) {
throw new \coding_exception('The \'discussionid\' value must be set in other.');
}
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if (!isset($this->other['forumtype'])) {
throw new \coding_exception('The \'forumtype\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_posts', 'restore' => 'forum_post');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
$othermapped['discussionid'] = array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
return $othermapped;
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum post deleted event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum post deleted event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int discussionid: The discussion id the post is part of.
* - int forumid: The forum id the post is part of.
* - string forumtype: The type of forum the post is part of.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class post_deleted extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'forum_posts';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has deleted the post with id '$this->objectid' in the discussion with " .
"id '{$this->other['discussionid']}' in the forum with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventpostdeleted', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
if ($this->other['forumtype'] == 'single') {
// Single discussion forums are an exception. We show
// the forum itself since it only has one discussion
// thread.
$url = new \moodle_url('/mod/forum/view.php', array('f' => $this->other['forumid']));
} else {
$url = new \moodle_url('/mod/forum/discuss.php', array('d' => $this->other['discussionid']));
}
return $url;
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['discussionid'])) {
throw new \coding_exception('The \'discussionid\' value must be set in other.');
}
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if (!isset($this->other['forumtype'])) {
throw new \coding_exception('The \'forumtype\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_posts', 'restore' => 'forum_post');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
$othermapped['discussionid'] = array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
return $othermapped;
}
}
+131
View File
@@ -0,0 +1,131 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum post updated event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum post updated event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int discussionid: The discussion id the post is part of.
* - int forumid: The forum id the post is part of.
* - string forumtype: The type of forum the post is part of.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class post_updated extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'forum_posts';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has updated the post with id '$this->objectid' in the discussion with " .
"id '{$this->other['discussionid']}' in the forum with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventpostupdated', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
if ($this->other['forumtype'] == 'single') {
// Single discussion forums are an exception. We show
// the forum itself since it only has one discussion
// thread.
$url = new \moodle_url('/mod/forum/view.php', array('f' => $this->other['forumid']));
} else {
$url = new \moodle_url('/mod/forum/discuss.php', array('d' => $this->other['discussionid']));
}
$url->set_anchor('p'.$this->objectid);
return $url;
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['discussionid'])) {
throw new \coding_exception('The \'discussionid\' value must be set in other.');
}
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if (!isset($this->other['forumtype'])) {
throw new \coding_exception('The \'forumtype\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'forum_posts', 'restore' => 'forum_post');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
$othermapped['discussionid'] = array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
return $othermapped;
}
}
@@ -0,0 +1,110 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum subscription created event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum subscription created event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int forumid: The id of the forum which read tracking has been disabled on.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class readtracking_disabled extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has disabled read tracking for the user with id '$this->relateduserid' " .
"in the forum with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventreadtrackingdisabled', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/view.php', array('f' => $this->other['forumid']));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
return $othermapped;
}
}
@@ -0,0 +1,110 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum read tracking enabled event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum read tracking enabled event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int forumid: The id of the forum which readtracking has been enabled on.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class readtracking_enabled extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has enabled read tracking for the user with id '$this->relateduserid' " .
"in the forum with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventreadtrackingenabled', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/view.php', array('f' => $this->other['forumid']));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
return $othermapped;
}
}
@@ -0,0 +1,108 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_forum subscribers list viewed event.
*
* @package mod_forum
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_forum\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_forum subscribers list viewed event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int forumid: The id of the forum which the subscriberslist has been viewed.
* }
*
* @package mod_forum
* @since Moodle 2.7
* @copyright 2014 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class subscribers_viewed extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has viewed the subscribers list for the forum with course " .
"module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsubscribersviewed', 'mod_forum');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/subscribers.php', array('id' => $this->other['forumid']));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['forumid'])) {
throw new \coding_exception('The \'forumid\' value must be set in other.');
}
if ($this->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Context level must be CONTEXT_MODULE.');
}
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
return $othermapped;
}
}

Some files were not shown because too many files have changed in this diff Show More