diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..81ba4368dd --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,53 @@ +> **Note:** Please fill out all required sections and remove irrelevant ones. +### πŸ”€ Purpose of this PR: + +- [ ] Fixes a bug +- [ ] Updates for a new Moodle version +- [ ] Adds a new feature of functionality +- [ ] Improves or enhances existing features +- [ ] Refactoring: restructures code for better performance or maintainability +- [ ] Testing: add missing or improve existing tests +- [ ] Miscellaneous: code cleaning (without functional changes), documentation, configuration, ... + +--- + +### πŸ“ Description: + +Please describe the purpose of this PR in a few sentences. + +- What feature or bug does it address? +- Why is this change or addition necessary? +- What is the expected behavior after the change? + +--- + +### πŸ“‹ Checklist + +Please confirm the following (check all that apply): + +- [ ] I have `phpunit` and/or `behat` tests that cover my changes or additions. +- [ ] Code passes the code checker without errors and warnings. +- [ ] Code passes the moodle-ci/cd pipeline on all supported Moodle versions or the ones the plugin supports. +- [ ] Code does not have `var_dump()` or `var_export` or any other debugging statements (or commented out code) that + should not appear on the productive branch. +- [ ] Code only uses language strings instead of hard-coded strings. +- [ ] If there are changes in the database: I updated/created the necessary upgrade steps in `db/upgrade.php` and + updated the `version.php`. +- [ ] If there are changes in javascript: I build new `.min` files with the `grunt amd` command. +- [ ] If it is a Moodle update PR: I read the release notes, updated the `version.php` and the `CHANGES.md`. + I ran all tests thoroughly checking for errors. I checked if bootstrap had any changes/deprecations that require + changes in the plugins UI. + +--- + +### πŸ” Related Issues + +- Related to #[IssueNumber] + +--- + +### πŸ§ΎπŸ“ΈπŸŒ Additional Information (like screenshots, documentation, links, etc.) + +Any other relevant information. + +--- \ No newline at end of file diff --git a/.github/workflows/config.json b/.github/workflows/config.json index 016b63376a..7a643bc495 100644 --- a/.github/workflows/config.json +++ b/.github/workflows/config.json @@ -1,11 +1,22 @@ { - "main-moodle": "MOODLE_405_STABLE", + "moodle-plugin-ci": "4.5.7", + "main-moodle": "MOODLE_500_STABLE", "main-php": "8.3", - "moodle-php": { - "MOODLE_401_STABLE": ["7.4", "8.1"], - "MOODLE_403_STABLE": ["8.0", "8.2"], - "MOODLE_404_STABLE": ["8.1", "8.3"], - "MOODLE_405_STABLE": ["8.1", "8.2", "8.3"] - }, - "moodle-plugin-ci": "4.5.5" -} + "main-db": "pgsql", + "moodle-testmatrix": { + "MOODLE_401_STABLE": { + "php": ["8.0", "8.1"] + }, + "MOODLE_404_STABLE": { + "php": ["8.1", "8.2", "8.3"] + }, + "MOODLE_405_STABLE": { + "php": ["8.1", "8.2", "8.3"], + "db": ["pgsql", "mariadb", "mysqli"] + }, + "MOODLE_500_STABLE": { + "php": ["8.2", "8.3", "8.4"], + "db": ["pgsql", "mariadb", "mysqli"] + } + } +} \ No newline at end of file diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml index 0b2eda27b5..023398ddf0 100644 --- a/.github/workflows/moodle-ci.yml +++ b/.github/workflows/moodle-ci.yml @@ -7,3 +7,4 @@ jobs: uses: learnweb/moodle-workflows-learnweb/.github/workflows/moodle-ci.yml@main with: allow-mustache-lint-error: true + allow-grunt-error: true diff --git a/CHANGES.md b/CHANGES.md new file mode 100755 index 0000000000..21d35d99cd --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,10 @@ +CHANGELOG +========= + +4.5.1 (2025-05-19) +------------------ +[HOTFIX] #214 + +4.5.0 (2025-05-06) +------------------ +* Moodle 4.5 compatible version \ No newline at end of file diff --git a/amd/build/activityhelp.min.js b/amd/build/activityhelp.min.js index a875b70b82..4e6f2f9007 100644 --- a/amd/build/activityhelp.min.js +++ b/amd/build/activityhelp.min.js @@ -1,4 +1,4 @@ -define("mod_moodleoverflow/activityhelp",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0; +define("mod_moodleoverflow/activityhelp",["exports"],function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0; /** * Show a help string for the amount of activity column in userstats_table.php * @@ -6,6 +6,6 @@ define("mod_moodleoverflow/activityhelp",["exports"],(function(_exports){Object. * @copyright 2023 Tamaro Walter * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const Selectors_actions={showHelpIcon:'[data-action="showhelpicon"]'};_exports.init=()=>{document.addEventListener("click",(event=>{event.target.closest(Selectors_actions.showHelpIcon)&&event.preventDefault()}))}})); +const Selectors_actions={showHelpIcon:'[data-action="showhelpicon"]'};_exports.init=()=>{document.addEventListener("click",event=>{event.target.closest(Selectors_actions.showHelpIcon)&&event.preventDefault()})}}); //# sourceMappingURL=activityhelp.min.js.map \ No newline at end of file diff --git a/amd/build/activityhelp.min.js.map b/amd/build/activityhelp.min.js.map index 64d79cd463..20737ae222 100644 --- a/amd/build/activityhelp.min.js.map +++ b/amd/build/activityhelp.min.js.map @@ -1 +1 @@ -{"version":3,"file":"activityhelp.min.js","sources":["../src/activityhelp.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Show a help string for the amount of activity column in userstats_table.php\n *\n * @module mod_moodleoverflow/activityhelp\n * @copyright 2023 Tamaro Walter\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\n\nconst Selectors = {\n actions: {\n showHelpIcon: '[data-action=\"showhelpicon\"]',\n },\n};\n\n/**\n * Function that shows the help string.\n */\nexport const init = () => {\n document.addEventListener('click', event => {\n if (event.target.closest(Selectors.actions.showHelpIcon)) {\n event.preventDefault();\n }\n });\n};"],"names":["Selectors","showHelpIcon","document","addEventListener","event","target","closest","preventDefault"],"mappings":";;;;;;;;MAyBMA,kBACO,CACLC,aAAc,8CAOF,KAChBC,SAASC,iBAAiB,SAASC,QAC3BA,MAAMC,OAAOC,QAAQN,kBAAkBC,eACvCG,MAAMG"} \ No newline at end of file +{"version":3,"file":"activityhelp.min.js","sources":["../src/activityhelp.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Show a help string for the amount of activity column in userstats_table.php\n *\n * @module mod_moodleoverflow/activityhelp\n * @copyright 2023 Tamaro Walter\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst Selectors = {\n actions: {\n showHelpIcon: '[data-action=\"showhelpicon\"]',\n },\n};\n\n/**\n * Function that shows the help icon string.\n */\nexport const init = () => {\n document.addEventListener('click', event => {\n if (event.target.closest(Selectors.actions.showHelpIcon)) {\n event.preventDefault();\n }\n });\n};"],"names":["Selectors","showHelpIcon","_exports","init","document","addEventListener","event","target","closest","preventDefault"],"mappings":";;;;;;;;AAuBA,MAAMA,kBACO,CACLC,aAAc,gCAapBC,SAAAC,KANkBA,KAChBC,SAASC,iBAAiB,QAASC,QAC3BA,MAAMC,OAAOC,QAAQR,kBAAkBC,eACvCK,MAAMG,mBAGhB"} \ No newline at end of file diff --git a/amd/build/rating.min.js b/amd/build/rating.min.js index 0e51924c2d..93a4db92d9 100644 --- a/amd/build/rating.min.js +++ b/amd/build/rating.min.js @@ -1,10 +1,10 @@ -define("mod_moodleoverflow/rating",["exports","core/ajax","core/prefetch","core/str"],(function(_exports,_ajax,_prefetch,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("mod_moodleoverflow/rating",["exports","core/ajax","core/prefetch","core/str"],function(_exports,_ajax,_prefetch,_str){function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}} /** * Implements rating functionality * * @module mod_moodleoverflow/rating * @copyright 2022 Justus Dieckmann WWU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(userid,allowmultiplemarks){_prefetch.default.prefetchStrings("mod_moodleoverflow",["marksolved","marknotsolved","markhelpful","marknothelpful","action_remove_upvote","action_upvote","action_remove_downvote","action_downvote"]),root.onclick=async event=>{const actionElement=event.target.closest("[data-moodleoverflow-action]");if(!actionElement)return;const action=actionElement.getAttribute("data-moodleoverflow-action"),postElement=actionElement.closest("[data-moodleoverflow-postid]"),postid=null==postElement?void 0:postElement.getAttribute("data-moodleoverflow-postid");switch(action){case"upvote":case"downvote":{const isupvote="upvote"===action;if("clicked"===actionElement.getAttribute("data-moodleoverflow-state"))await sendVote(postid,isupvote?20:10,userid),actionElement.setAttribute("data-moodleoverflow-state","notclicked"),actionElement.title=await(0,_str.get_string)("action_"+action,"mod_moodleoverflow");else{const otherAction=isupvote?"downvote":"upvote";await sendVote(postid,isupvote?2:1,userid),actionElement.setAttribute("data-moodleoverflow-state","clicked");const otherElement=postElement.querySelector('[data-moodleoverflow-action="'.concat(otherAction,'"]'));otherElement.setAttribute("data-moodleoverflow-state","notclicked"),actionElement.title=await(0,_str.get_string)("action_remove_"+action,"mod_moodleoverflow"),otherElement.title=await(0,_str.get_string)("action_"+otherAction,"mod_moodleoverflow")}}break;case"helpful":case"solved":{const isHelpful="helpful"===action,htmlclass=isHelpful?"markedhelpful":"markedsolution",shouldRemove=postElement.classList.contains(htmlclass),baseRating=isHelpful?4:3,rating=shouldRemove?10*baseRating:baseRating;if(await sendVote(postid,rating,userid),allowmultiplemarks)shouldRemove&&(postElement.classList.remove(htmlclass),actionElement.textContent=await(0,_str.get_string)("mark".concat(action),"mod_moodleoverflow"),changeStrings(htmlclass,action));else for(const el of root.querySelectorAll(".moodleoverflowpost."+htmlclass))el.classList.remove(htmlclass),el.querySelector('[data-moodleoverflow-action="'.concat(action,'"]')).textContent=await(0,_str.get_string)("mark".concat(action),"mod_moodleoverflow");shouldRemove||(postElement.classList.add(htmlclass),actionElement.textContent=await(0,_str.get_string)("marknot".concat(action),"mod_moodleoverflow"),allowmultiplemarks&&changeStrings(htmlclass,action))}}}},_ajax=_interopRequireDefault(_ajax),_prefetch=_interopRequireDefault(_prefetch);const root=document.getElementById("moodleoverflow-root");async function sendVote(postid,rating,userid){const response=await _ajax.default.call([{methodname:"mod_moodleoverflow_record_vote",args:{postid:postid,ratingid:rating}}])[0];return root.querySelectorAll('[data-moodleoverflow-userreputation="'.concat(userid,'"]')).forEach((i=>{i.textContent=response.raterreputation})),root.querySelectorAll('[data-moodleoverflow-userreputation="'.concat(response.ownerid,'"]')).forEach((i=>{i.textContent=response.ownerreputation})),root.querySelectorAll('[data-moodleoverflow-postreputation="'.concat(postid,'"]')).forEach((i=>{i.textContent=response.postrating})),response}async function changeStrings(htmlclass,action){_prefetch.default.prefetchStrings("mod_moodleoverflow",["marksolved","alsomarksolved","markhelpful","alsomarkhelpful"]);var othermarkedposts=!1;for(const el of root.querySelectorAll(".moodleoverflowpost"))if(el.classList.contains(htmlclass)){othermarkedposts=!0;break}for(const el of root.querySelectorAll(".moodleoverflowpost"))!el.classList.contains(htmlclass)&&el.querySelector('[data-moodleoverflow-action="'.concat(action,'"]'))&&(el.querySelector('[data-moodleoverflow-action="'.concat(action,'"]')).textContent=othermarkedposts?await(0,_str.get_string)("alsomark".concat(action),"mod_moodleoverflow"):await(0,_str.get_string)("mark".concat(action),"mod_moodleoverflow"))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(userid,allowmultiplemarks){_prefetch.default.prefetchStrings("mod_moodleoverflow",["marksolved","marknotsolved","markhelpful","marknothelpful","action_remove_upvote","action_upvote","action_remove_downvote","action_downvote"]),root.onclick=async event=>{const actionElement=event.target.closest("[data-moodleoverflow-action]");if(!actionElement)return;const action=actionElement.getAttribute("data-moodleoverflow-action"),postElement=actionElement.closest("[data-moodleoverflow-postid]"),postid=null==postElement?void 0:postElement.getAttribute("data-moodleoverflow-postid");switch(action){case"upvote":case"downvote":{const isupvote="upvote"===action;if("clicked"===actionElement.getAttribute("data-moodleoverflow-state"))await sendVote(postid,isupvote?RATING_REMOVE_UPVOTE:RATING_REMOVE_DOWNVOTE,userid),actionElement.setAttribute("data-moodleoverflow-state","notclicked"),actionElement.title=await(0,_str.get_string)("action_"+action,"mod_moodleoverflow");else{const otherAction=isupvote?"downvote":"upvote";await sendVote(postid,isupvote?RATING_UPVOTE:RATING_DOWNVOTE,userid),actionElement.setAttribute("data-moodleoverflow-state","clicked");const otherElement=postElement.querySelector(`[data-moodleoverflow-action="${otherAction}"]`);otherElement.setAttribute("data-moodleoverflow-state","notclicked"),actionElement.title=await(0,_str.get_string)("action_remove_"+action,"mod_moodleoverflow"),otherElement.title=await(0,_str.get_string)("action_"+otherAction,"mod_moodleoverflow")}}break;case"helpful":case"solved":{const isHelpful="helpful"===action,htmlclass=isHelpful?"markedhelpful":"markedsolution",shouldRemove=postElement.classList.contains(htmlclass),baseRating=isHelpful?RATING_HELPFUL:RATING_SOLVED,rating=shouldRemove?10*baseRating:baseRating;if(await sendVote(postid,rating,userid),allowmultiplemarks)shouldRemove&&(postElement.classList.remove(htmlclass),actionElement.textContent=await(0,_str.get_string)(`mark${action}`,"mod_moodleoverflow"),changeStrings(htmlclass,action));else for(const el of root.querySelectorAll(".moodleoverflowpost."+htmlclass))el.classList.remove(htmlclass),el.querySelector(`[data-moodleoverflow-action="${action}"]`).textContent=await(0,_str.get_string)(`mark${action}`,"mod_moodleoverflow");shouldRemove||(postElement.classList.add(htmlclass),actionElement.textContent=await(0,_str.get_string)(`marknot${action}`,"mod_moodleoverflow"),allowmultiplemarks&&changeStrings(htmlclass,action))}}}},_ajax=_interopRequireDefault(_ajax),_prefetch=_interopRequireDefault(_prefetch);const RATING_DOWNVOTE=1,RATING_UPVOTE=2,RATING_REMOVE_DOWNVOTE=10,RATING_REMOVE_UPVOTE=20,RATING_SOLVED=3,RATING_HELPFUL=4,root=document.getElementById("moodleoverflow-root");async function sendVote(postid,rating,userid){const response=await _ajax.default.call([{methodname:"mod_moodleoverflow_record_vote",args:{postid:postid,ratingid:rating}}])[0];return root.querySelectorAll(`[data-moodleoverflow-userreputation="${userid}"]`).forEach(i=>{i.textContent=response.raterreputation}),root.querySelectorAll(`[data-moodleoverflow-userreputation="${response.ownerid}"]`).forEach(i=>{i.textContent=response.ownerreputation}),root.querySelectorAll(`[data-moodleoverflow-postreputation="${postid}"]`).forEach(i=>{i.textContent=response.postrating}),response}async function changeStrings(htmlclass,action){_prefetch.default.prefetchStrings("mod_moodleoverflow",["marksolved","alsomarksolved","markhelpful","alsomarkhelpful"]);var othermarkedposts=!1;for(const el of root.querySelectorAll(".moodleoverflowpost"))if(el.classList.contains(htmlclass)){othermarkedposts=!0;break}for(const el of root.querySelectorAll(".moodleoverflowpost"))!el.classList.contains(htmlclass)&&el.querySelector(`[data-moodleoverflow-action="${action}"]`)&&(el.querySelector(`[data-moodleoverflow-action="${action}"]`).textContent=othermarkedposts?await(0,_str.get_string)(`alsomark${action}`,"mod_moodleoverflow"):await(0,_str.get_string)(`mark${action}`,"mod_moodleoverflow"))}}); //# sourceMappingURL=rating.min.js.map \ No newline at end of file diff --git a/amd/build/rating.min.js.map b/amd/build/rating.min.js.map index bd1b71de82..76825615bf 100644 --- a/amd/build/rating.min.js.map +++ b/amd/build/rating.min.js.map @@ -1 +1 @@ -{"version":3,"file":"rating.min.js","sources":["../src/rating.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Implements rating functionality\n *\n * @module mod_moodleoverflow/rating\n * @copyright 2022 Justus Dieckmann WWU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\nimport Prefetch from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\n\nconst RATING_DOWNVOTE = 1;\nconst RATING_UPVOTE = 2;\nconst RATING_REMOVE_DOWNVOTE = 10;\nconst RATING_REMOVE_UPVOTE = 20;\nconst RATING_SOLVED = 3;\nconst RATING_HELPFUL = 4;\n\nconst root = document.getElementById('moodleoverflow-root');\n\n/**\n * Send a vote via AJAX, then updates post and user ratings.\n * @param {int} postid\n * @param {int} rating\n * @param {int} userid\n * @returns {Promise<*>}\n */\nasync function sendVote(postid, rating, userid) {\n const response = await Ajax.call([{\n methodname: 'mod_moodleoverflow_record_vote',\n args: {\n postid: postid,\n ratingid: rating\n }\n }])[0];\n root.querySelectorAll(`[data-moodleoverflow-userreputation=\"${userid}\"]`).forEach((i) => {\n i.textContent = response.raterreputation;\n });\n root.querySelectorAll(`[data-moodleoverflow-userreputation=\"${response.ownerid}\"]`).forEach((i) => {\n i.textContent = response.ownerreputation;\n });\n root.querySelectorAll(`[data-moodleoverflow-postreputation=\"${postid}\"]`).forEach((i) => {\n i.textContent = response.postrating;\n });\n return response;\n}\n\n\n/**\n * Init function.\n *\n * @param {int} userid\n * @param {boolean} allowmultiplemarks // true means allowed, false means not allowed.\n *\n */\nexport function init(userid, allowmultiplemarks) {\n Prefetch.prefetchStrings('mod_moodleoverflow',\n ['marksolved', 'marknotsolved', 'markhelpful', 'marknothelpful',\n 'action_remove_upvote', 'action_upvote', 'action_remove_downvote', 'action_downvote']);\n\n root.onclick = async(event) => {\n const actionElement = event.target.closest('[data-moodleoverflow-action]');\n if (!actionElement) {\n return;\n }\n\n const action = actionElement.getAttribute('data-moodleoverflow-action');\n const postElement = actionElement.closest('[data-moodleoverflow-postid]');\n const postid = postElement?.getAttribute('data-moodleoverflow-postid');\n\n switch (action) {\n case 'upvote':\n case 'downvote': {\n const isupvote = action === 'upvote';\n if (actionElement.getAttribute('data-moodleoverflow-state') === 'clicked') {\n await sendVote(postid, isupvote ? RATING_REMOVE_UPVOTE : RATING_REMOVE_DOWNVOTE, userid);\n actionElement.setAttribute('data-moodleoverflow-state', 'notclicked');\n actionElement.title = await getString('action_' + action, 'mod_moodleoverflow');\n } else {\n const otherAction = isupvote ? 'downvote' : 'upvote';\n await sendVote(postid, isupvote ? RATING_UPVOTE : RATING_DOWNVOTE, userid);\n actionElement.setAttribute('data-moodleoverflow-state', 'clicked');\n const otherElement = postElement.querySelector(\n `[data-moodleoverflow-action=\"${otherAction}\"]`);\n otherElement.setAttribute('data-moodleoverflow-state', 'notclicked');\n actionElement.title = await getString('action_remove_' + action, 'mod_moodleoverflow');\n otherElement.title = await getString('action_' + otherAction, 'mod_moodleoverflow');\n }\n }\n break;\n case 'helpful':\n case 'solved': {\n const isHelpful = action === 'helpful';\n const htmlclass = isHelpful ? 'markedhelpful' : 'markedsolution';\n const shouldRemove = postElement.classList.contains(htmlclass);\n const baseRating = isHelpful ? RATING_HELPFUL : RATING_SOLVED;\n const rating = shouldRemove ? baseRating * 10 : baseRating;\n await sendVote(postid, rating, userid);\n\n /* If multiplemarks are not allowed (that is the default mode): delete all marks.\n else: only delete the mark if the post is being unmarked.\n\n Add a mark, if the post is being marked.\n */\n if (!allowmultiplemarks) {\n // Delete all marks in the discussion\n for (const el of root.querySelectorAll('.moodleoverflowpost.' + htmlclass)) {\n el.classList.remove(htmlclass);\n el.querySelector(`[data-moodleoverflow-action=\"${action}\"]`).textContent =\n await getString(`mark${action}`, 'mod_moodleoverflow');\n }\n } else {\n // Remove only the mark of the unmarked post.\n if (shouldRemove) {\n postElement.classList.remove(htmlclass);\n actionElement.textContent = await getString(`mark${action}`, 'mod_moodleoverflow');\n changeStrings(htmlclass, action);\n }\n }\n // If the post is being marked, mark it.\n if (!shouldRemove) {\n postElement.classList.add(htmlclass);\n actionElement.textContent = await getString(`marknot${action}`, 'mod_moodleoverflow');\n if (allowmultiplemarks) {\n changeStrings(htmlclass, action);\n }\n }\n\n }\n }\n };\n\n}\n\n/**\n * Function to change the String of the post data-action button.\n * Only used if mulitplemarks are allowed.\n * @param {string} htmlclass the class where the String is being updated\n * @param {string} action helpful or solved mark\n */\nasync function changeStrings(htmlclass, action) {\n Prefetch.prefetchStrings('mod_moodleoverflow',\n ['marksolved', 'alsomarksolved', 'markhelpful', 'alsomarkhelpful',]);\n\n // 1. Step: Are there other posts in the Discussion, that are solved/helpful?\n var othermarkedposts = false;\n for (const el of root.querySelectorAll('.moodleoverflowpost')) {\n if (el.classList.contains(htmlclass)) {\n othermarkedposts = true;\n break;\n }\n }\n // 2. Step: Change the strings of the action Button of the unmarked posts.\n for (const el of root.querySelectorAll('.moodleoverflowpost')) {\n if (!el.classList.contains(htmlclass) && el.querySelector(`[data-moodleoverflow-action=\"${action}\"]`)) {\n if (othermarkedposts) {\n el.querySelector(`[data-moodleoverflow-action=\"${action}\"]`).textContent =\n await getString(`alsomark${action}`, 'mod_moodleoverflow');\n } else {\n el.querySelector(`[data-moodleoverflow-action=\"${action}\"]`).textContent =\n await getString(`mark${action}`, 'mod_moodleoverflow');\n }\n }\n }\n}"],"names":["userid","allowmultiplemarks","prefetchStrings","root","onclick","async","actionElement","event","target","closest","action","getAttribute","postElement","postid","isupvote","sendVote","setAttribute","title","otherAction","otherElement","querySelector","isHelpful","htmlclass","shouldRemove","classList","contains","baseRating","rating","remove","textContent","changeStrings","el","querySelectorAll","add","document","getElementById","response","Ajax","call","methodname","args","ratingid","forEach","i","raterreputation","ownerid","ownerreputation","postrating","othermarkedposts"],"mappings":";;;;;;;oFAsEqBA,OAAQC,sCAChBC,gBAAgB,qBACrB,CAAC,aAAc,gBAAiB,cAAe,iBAC3C,uBAAwB,gBAAiB,yBAA0B,oBAE3EC,KAAKC,QAAUC,MAAAA,cACLC,cAAgBC,MAAMC,OAAOC,QAAQ,oCACtCH,2BAICI,OAASJ,cAAcK,aAAa,8BACpCC,YAAcN,cAAcG,QAAQ,gCACpCI,OAASD,MAAAA,mBAAAA,YAAaD,aAAa,qCAEjCD,YACC,aACA,kBACKI,SAAsB,WAAXJ,UAC+C,YAA5DJ,cAAcK,aAAa,mCACrBI,SAASF,OAAQC,SA7Dd,GADE,GA8DsEd,QACjFM,cAAcU,aAAa,4BAA6B,cACxDV,cAAcW,YAAc,mBAAU,UAAYP,OAAQ,0BACvD,OACGQ,YAAcJ,SAAW,WAAa,eACtCC,SAASF,OAAQC,SApErB,EADE,EAqE+Dd,QACnEM,cAAcU,aAAa,4BAA6B,iBAClDG,aAAeP,YAAYQ,qDACGF,mBACpCC,aAAaH,aAAa,4BAA6B,cACvDV,cAAcW,YAAc,mBAAU,iBAAmBP,OAAQ,sBACjES,aAAaF,YAAc,mBAAU,UAAYC,YAAa,iCAIjE,cACA,gBACKG,UAAuB,YAAXX,OACZY,UAAYD,UAAY,gBAAkB,iBAC1CE,aAAeX,YAAYY,UAAUC,SAASH,WAC9CI,WAAaL,UA/EZ,EADD,EAiFAM,OAASJ,aAA4B,GAAbG,WAAkBA,oBAC1CX,SAASF,OAAQc,OAAQ3B,QAO1BC,mBASGsB,eACAX,YAAYY,UAAUI,OAAON,WAC7BhB,cAAcuB,kBAAoB,iCAAiBnB,QAAU,sBAC7DoB,cAAcR,UAAWZ,kBAVxB,MAAMqB,MAAM5B,KAAK6B,iBAAiB,uBAAyBV,WAC5DS,GAAGP,UAAUI,OAAON,WACpBS,GAAGX,qDAA8CV,cAAYmB,kBACnD,iCAAiBnB,QAAU,sBAWxCa,eACDX,YAAYY,UAAUS,IAAIX,WAC1BhB,cAAcuB,kBAAoB,oCAAoBnB,QAAU,sBAC5DT,oBACA6B,cAAcR,UAAWZ,mGA1G3CP,KAAO+B,SAASC,eAAe,sCAStBpB,SAASF,OAAQc,OAAQ3B,cAC9BoC,eAAiBC,cAAKC,KAAK,CAAC,CAC9BC,WAAY,iCACZC,KAAM,CACF3B,OAAQA,OACR4B,SAAUd,WAEd,UACJxB,KAAK6B,gEAAyDhC,cAAY0C,SAASC,IAC/EA,EAAEd,YAAcO,SAASQ,mBAE7BzC,KAAK6B,gEAAyDI,SAASS,eAAaH,SAASC,IACzFA,EAAEd,YAAcO,SAASU,mBAE7B3C,KAAK6B,gEAAyDnB,cAAY6B,SAASC,IAC/EA,EAAEd,YAAcO,SAASW,cAEtBX,wBAgGIN,cAAcR,UAAWZ,0BAC3BR,gBAAgB,qBACrB,CAAC,aAAc,iBAAkB,cAAe,wBAGhD8C,kBAAmB,MAClB,MAAMjB,MAAM5B,KAAK6B,iBAAiB,0BAC/BD,GAAGP,UAAUC,SAASH,WAAY,CAClC0B,kBAAmB,YAKtB,MAAMjB,MAAM5B,KAAK6B,iBAAiB,wBAC9BD,GAAGP,UAAUC,SAASH,YAAcS,GAAGX,qDAA8CV,gBAElFqB,GAAGX,qDAA8CV,cAAYmB,YAD7DmB,uBAEU,qCAAqBtC,QAAU,4BAG/B,iCAAiBA,QAAU"} \ No newline at end of file +{"version":3,"file":"rating.min.js","sources":["../src/rating.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Implements rating functionality\n *\n * @module mod_moodleoverflow/rating\n * @copyright 2022 Justus Dieckmann WWU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\nimport Prefetch from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\n\nconst RATING_DOWNVOTE = 1;\nconst RATING_UPVOTE = 2;\nconst RATING_REMOVE_DOWNVOTE = 10;\nconst RATING_REMOVE_UPVOTE = 20;\nconst RATING_SOLVED = 3;\nconst RATING_HELPFUL = 4;\n\nconst root = document.getElementById('moodleoverflow-root');\n\n/**\n * Send a vote via AJAX, then updates post and user ratings.\n * @param {int} postid\n * @param {int} rating\n * @param {int} userid\n * @returns {Promise<*>}\n */\nasync function sendVote(postid, rating, userid) {\n const response = await Ajax.call([{\n methodname: 'mod_moodleoverflow_record_vote',\n args: {\n postid: postid,\n ratingid: rating\n }\n }])[0];\n\n root.querySelectorAll(`[data-moodleoverflow-userreputation=\"${userid}\"]`).forEach((i) => {\n i.textContent = response.raterreputation;\n });\n\n root.querySelectorAll(`[data-moodleoverflow-userreputation=\"${response.ownerid}\"]`).forEach((i) => {\n i.textContent = response.ownerreputation;\n });\n\n root.querySelectorAll(`[data-moodleoverflow-postreputation=\"${postid}\"]`).forEach((i) => {\n i.textContent = response.postrating;\n });\n\n return response;\n}\n\n\n/**\n * Init function.\n *\n * @param {int} userid\n * @param {boolean} allowmultiplemarks // true means allowed, false means not allowed.\n *\n */\nexport function init(userid, allowmultiplemarks) {\n Prefetch.prefetchStrings('mod_moodleoverflow',\n ['marksolved', 'marknotsolved', 'markhelpful', 'marknothelpful',\n 'action_remove_upvote', 'action_upvote', 'action_remove_downvote', 'action_downvote']);\n\n root.onclick = async(event) => {\n const actionElement = event.target.closest('[data-moodleoverflow-action]');\n if (!actionElement) {\n return;\n }\n\n const action = actionElement.getAttribute('data-moodleoverflow-action');\n const postElement = actionElement.closest('[data-moodleoverflow-postid]');\n const postid = postElement?.getAttribute('data-moodleoverflow-postid');\n\n switch (action) {\n case 'upvote':\n case 'downvote': {\n const isupvote = action === 'upvote';\n if (actionElement.getAttribute('data-moodleoverflow-state') === 'clicked') {\n await sendVote(postid, isupvote ? RATING_REMOVE_UPVOTE : RATING_REMOVE_DOWNVOTE, userid);\n actionElement.setAttribute('data-moodleoverflow-state', 'notclicked');\n actionElement.title = await getString('action_' + action, 'mod_moodleoverflow');\n } else {\n const otherAction = isupvote ? 'downvote' : 'upvote';\n await sendVote(postid, isupvote ? RATING_UPVOTE : RATING_DOWNVOTE, userid);\n actionElement.setAttribute('data-moodleoverflow-state', 'clicked');\n const otherElement = postElement.querySelector(\n `[data-moodleoverflow-action=\"${otherAction}\"]`);\n otherElement.setAttribute('data-moodleoverflow-state', 'notclicked');\n actionElement.title = await getString('action_remove_' + action, 'mod_moodleoverflow');\n otherElement.title = await getString('action_' + otherAction, 'mod_moodleoverflow');\n }\n }\n break;\n case 'helpful':\n case 'solved': {\n const isHelpful = action === 'helpful';\n const htmlclass = isHelpful ? 'markedhelpful' : 'markedsolution';\n const shouldRemove = postElement.classList.contains(htmlclass);\n const baseRating = isHelpful ? RATING_HELPFUL : RATING_SOLVED;\n const rating = shouldRemove ? baseRating * 10 : baseRating;\n await sendVote(postid, rating, userid);\n\n /* If multiplemarks are not allowed (that is the default mode): delete all marks.\n else: only delete the mark if the post is being unmarked.\n\n Add a mark, if the post is being marked.\n */\n if (!allowmultiplemarks) {\n // Delete all marks in the discussion\n for (const el of root.querySelectorAll('.moodleoverflowpost.' + htmlclass)) {\n el.classList.remove(htmlclass);\n el.querySelector(`[data-moodleoverflow-action=\"${action}\"]`).textContent =\n await getString(`mark${action}`, 'mod_moodleoverflow');\n }\n } else {\n // Remove only the mark of the unmarked post.\n if (shouldRemove) {\n postElement.classList.remove(htmlclass);\n actionElement.textContent = await getString(`mark${action}`, 'mod_moodleoverflow');\n changeStrings(htmlclass, action);\n }\n }\n // If the post is being marked, mark it.\n if (!shouldRemove) {\n postElement.classList.add(htmlclass);\n actionElement.textContent = await getString(`marknot${action}`, 'mod_moodleoverflow');\n if (allowmultiplemarks) {\n changeStrings(htmlclass, action);\n }\n }\n\n }\n }\n };\n\n}\n\n/**\n * Function to change the String of the post data-action button.\n * Only used if multiplemarks are allowed.\n * @param {string} htmlclass the class where the String is being updated\n * @param {string} action helpful or solved mark\n */\nasync function changeStrings(htmlclass, action) {\n Prefetch.prefetchStrings('mod_moodleoverflow',\n ['marksolved', 'alsomarksolved', 'markhelpful', 'alsomarkhelpful',]);\n\n // 1. Step: Are there other posts in the Discussion, that are solved/helpful?\n var othermarkedposts = false;\n for (const el of root.querySelectorAll('.moodleoverflowpost')) {\n if (el.classList.contains(htmlclass)) {\n othermarkedposts = true;\n break;\n }\n }\n // 2. Step: Change the strings of the action Button of the unmarked posts.\n for (const el of root.querySelectorAll('.moodleoverflowpost')) {\n if (!el.classList.contains(htmlclass) && el.querySelector(`[data-moodleoverflow-action=\"${action}\"]`)) {\n if (othermarkedposts) {\n el.querySelector(`[data-moodleoverflow-action=\"${action}\"]`).textContent =\n await getString(`alsomark${action}`, 'mod_moodleoverflow');\n } else {\n el.querySelector(`[data-moodleoverflow-action=\"${action}\"]`).textContent =\n await getString(`mark${action}`, 'mod_moodleoverflow');\n }\n }\n }\n}"],"names":["_interopRequireDefault","e","__esModule","default","userid","allowmultiplemarks","Prefetch","prefetchStrings","root","onclick","async","actionElement","event","target","closest","action","getAttribute","postElement","postid","isupvote","sendVote","RATING_REMOVE_UPVOTE","RATING_REMOVE_DOWNVOTE","setAttribute","title","getString","otherAction","RATING_UPVOTE","RATING_DOWNVOTE","otherElement","querySelector","isHelpful","htmlclass","shouldRemove","classList","contains","baseRating","RATING_HELPFUL","RATING_SOLVED","rating","remove","textContent","changeStrings","el","querySelectorAll","add","_ajax","_prefetch","document","getElementById","response","Ajax","call","methodname","args","ratingid","forEach","i","raterreputation","ownerid","ownerreputation","postrating","othermarkedposts"],"mappings":"8HAuBqC,SAAAA,uBAAAC,GAAAA,OAAAA,GAAAA,EAAAC,WAAAD,EAAAE,CAAAA,QAAAF,EAAA;;;;;;;2EAmD9B,SAAcG,OAAQC,oBACzBC,UAAQH,QAACI,gBAAgB,qBACrB,CAAC,aAAc,gBAAiB,cAAe,iBAC3C,uBAAwB,gBAAiB,yBAA0B,oBAE3EC,KAAKC,QAAUC,cACX,MAAMC,cAAgBC,MAAMC,OAAOC,QAAQ,gCAC3C,IAAKH,cACD,OAGJ,MAAMI,OAASJ,cAAcK,aAAa,8BACpCC,YAAcN,cAAcG,QAAQ,gCACpCI,OAASD,uBAAAA,EAAAA,YAAaD,aAAa,8BAEzC,OAAQD,QACJ,IAAK,SACL,IAAK,WAAY,CACb,MAAMI,SAAsB,WAAXJ,OACjB,GAAgE,YAA5DJ,cAAcK,aAAa,mCACrBI,SAASF,OAAQC,SAAWE,qBAAuBC,uBAAwBlB,QACjFO,cAAcY,aAAa,4BAA6B,cACxDZ,cAAca,YAAc,EAAAC,KAAAA,YAAU,UAAYV,OAAQ,0BACvD,CACH,MAAMW,YAAcP,SAAW,WAAa,eACtCC,SAASF,OAAQC,SAAWQ,cAAgBC,gBAAiBxB,QACnEO,cAAcY,aAAa,4BAA6B,WACxD,MAAMM,aAAeZ,YAAYa,cAC7B,gCAAgCJ,iBACpCG,aAAaN,aAAa,4BAA6B,cACvDZ,cAAca,YAAc,EAAAC,KAAAA,YAAU,iBAAmBV,OAAQ,sBACjEc,aAAaL,YAAc,EAAAC,KAAAA,YAAU,UAAYC,YAAa,qBAClE,CACJ,CACA,MACA,IAAK,UACL,IAAK,SAAU,CACX,MAAMK,UAAuB,YAAXhB,OACZiB,UAAYD,UAAY,gBAAkB,iBAC1CE,aAAehB,YAAYiB,UAAUC,SAASH,WAC9CI,WAAaL,UAAYM,eAAiBC,cAC1CC,OAASN,aAA4B,GAAbG,WAAkBA,WAQhD,SAPMhB,SAASF,OAAQqB,OAAQnC,QAO1BC,mBASG4B,eACAhB,YAAYiB,UAAUM,OAAOR,WAC7BrB,cAAc8B,kBAAoB,EAAAhB,KAAAA,YAAU,OAAOV,SAAU,sBAC7D2B,cAAcV,UAAWjB,cAV7B,IAAK,MAAM4B,MAAMnC,KAAKoC,iBAAiB,uBAAyBZ,WAC5DW,GAAGT,UAAUM,OAAOR,WACpBW,GAAGb,cAAc,gCAAgCf,YAAY0B,kBACnD,EAAAhB,iBAAU,OAAOV,SAAU,sBAWxCkB,eACDhB,YAAYiB,UAAUW,IAAIb,WAC1BrB,cAAc8B,kBAAoB,EAAAhB,KAAAA,YAAU,UAAUV,SAAU,sBAC5DV,oBACAqC,cAAcV,UAAWjB,QAIrC,GAIZ,EAjIA+B,MAAA9C,uBAAA8C,OACAC,UAAA/C,uBAAA+C,WAGA,MAAMnB,gBAAkB,EAClBD,cAAgB,EAChBL,uBAAyB,GACzBD,qBAAuB,GACvBiB,cAAgB,EAChBD,eAAiB,EAEjB7B,KAAOwC,SAASC,eAAe,uBASrCvC,eAAeU,SAASF,OAAQqB,OAAQnC,QACpC,MAAM8C,eAAiBC,cAAKC,KAAK,CAAC,CAC9BC,WAAY,iCACZC,KAAM,CACFpC,OAAQA,OACRqC,SAAUhB,WAEd,GAcJ,OAZA/B,KAAKoC,iBAAiB,wCAAwCxC,YAAYoD,QAASC,IAC/EA,EAAEhB,YAAcS,SAASQ,kBAG7BlD,KAAKoC,iBAAiB,wCAAwCM,SAASS,aAAaH,QAASC,IACzFA,EAAEhB,YAAcS,SAASU,kBAG7BpD,KAAKoC,iBAAiB,wCAAwC1B,YAAYsC,QAASC,IAC/EA,EAAEhB,YAAcS,SAASW,aAGtBX,QACX,CA+FAxC,eAAegC,cAAcV,UAAWjB,QACpCT,UAAAA,QAASC,gBAAgB,qBACrB,CAAC,aAAc,iBAAkB,cAAe,oBAGpD,IAAIuD,kBAAmB,EACvB,IAAK,MAAMnB,MAAMnC,KAAKoC,iBAAiB,uBACnC,GAAID,GAAGT,UAAUC,SAASH,WAAY,CAClC8B,kBAAmB,EACnB,KACJ,CAGJ,IAAK,MAAMnB,MAAMnC,KAAKoC,iBAAiB,wBAC9BD,GAAGT,UAAUC,SAASH,YAAcW,GAAGb,cAAc,gCAAgCf,cAElF4B,GAAGb,cAAc,gCAAgCf,YAAY0B,YAD7DqB,uBAEU,EAAArC,iBAAU,WAAWV,SAAU,4BAG/B,EAAAU,iBAAU,OAAOV,SAAU,sBAIrD,CAAC"} \ No newline at end of file diff --git a/amd/build/reviewing.min.js b/amd/build/reviewing.min.js index 7dc1ac1e4f..d027cc1635 100644 --- a/amd/build/reviewing.min.js +++ b/amd/build/reviewing.min.js @@ -1,10 +1,10 @@ -define("mod_moodleoverflow/reviewing",["exports","core/ajax","core/prefetch","core/templates","core/str"],(function(_exports,_ajax,_prefetch,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("mod_moodleoverflow/reviewing",["exports","core/ajax","core/prefetch","core/templates","core/str"],function(_exports,_ajax,_prefetch,_templates,_str){function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}} /** * Implements reviewing functionality * * @module mod_moodleoverflow/reviewing * @copyright 2022 Justus Dieckmann WWU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){_prefetch.default.prefetchTemplates(["mod_moodleoverflow/reject_post_form","mod_moodleoverflow/review_buttons"]),_prefetch.default.prefetchStrings("mod_moodleoverflow",["post_was_approved","jump_to_next_post_needing_review","there_are_no_posts_needing_review","post_was_rejected"]);document.getElementById("moodleoverflow-posts").onclick=async e=>{const action=e.target.getAttribute("data-moodleoverflow-action");if(!action)return;const post=e.target.closest("*[data-moodleoverflow-postid]"),reviewRow=e.target.closest(".reviewrow"),postID=post.getAttribute("data-moodleoverflow-postid");if("approve"===action){reviewRow.innerHTML=".";const nextPostURL=await _ajax.default.call([{methodname:"mod_moodleoverflow_review_approve_post",args:{postid:postID}}])[0];let message=await(0,_str.get_string)("post_was_approved","mod_moodleoverflow")+" ";message+=nextPostURL?'')+await(0,_str.get_string)("jump_to_next_post_needing_review","mod_moodleoverflow")+"":await(0,_str.get_string)("there_are_no_posts_needing_review","mod_moodleoverflow"),reviewRow.innerHTML=message,post.classList.remove("pendingreview")}else if("reject"===action)reviewRow.innerHTML=".",reviewRow.innerHTML=await _templates.default.render("mod_moodleoverflow/reject_post_form",{});else if("reject-submit"===action){const rejectMessage=post.querySelector("textarea.reject-reason").value.toString().trim();reviewRow.innerHTML=".";const args={postid:postID,reason:rejectMessage||null},nextPostURL=await _ajax.default.call([{methodname:"mod_moodleoverflow_review_reject_post",args:args}])[0];let message=await(0,_str.get_string)("post_was_rejected","mod_moodleoverflow")+" ";message+=nextPostURL?'')+await(0,_str.get_string)("jump_to_next_post_needing_review","mod_moodleoverflow")+"":await(0,_str.get_string)("there_are_no_posts_needing_review","mod_moodleoverflow"),reviewRow.innerHTML=message}else"reject-cancel"===action&&(reviewRow.innerHTML=".",reviewRow.innerHTML=await _templates.default.render("mod_moodleoverflow/review_buttons",{}))}},_ajax=_interopRequireDefault(_ajax),_prefetch=_interopRequireDefault(_prefetch),_templates=_interopRequireDefault(_templates)})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){_prefetch.default.prefetchTemplates(["mod_moodleoverflow/reject_post_form","mod_moodleoverflow/review_buttons"]),_prefetch.default.prefetchStrings("mod_moodleoverflow",["post_was_approved","jump_to_next_post_needing_review","there_are_no_posts_needing_review","post_was_rejected"]);document.getElementById("moodleoverflow-posts").onclick=async e=>{const action=e.target.getAttribute("data-moodleoverflow-action");if(!action)return;const post=e.target.closest("*[data-moodleoverflow-postid]"),reviewRow=e.target.closest(".reviewrow"),postID=post.getAttribute("data-moodleoverflow-postid");if("approve"===action){reviewRow.innerHTML=".";const nextPostURL=await _ajax.default.call([{methodname:"mod_moodleoverflow_review_approve_post",args:{postid:postID}}])[0];let message=await(0,_str.get_string)("post_was_approved","mod_moodleoverflow")+" ";message+=nextPostURL?``+await(0,_str.get_string)("jump_to_next_post_needing_review","mod_moodleoverflow")+"":await(0,_str.get_string)("there_are_no_posts_needing_review","mod_moodleoverflow"),reviewRow.innerHTML=message,post.classList.remove("pendingreview")}else if("reject"===action)reviewRow.innerHTML=".",reviewRow.innerHTML=await _templates.default.render("mod_moodleoverflow/reject_post_form",{});else if("reject-submit"===action){const rejectMessage=post.querySelector("textarea.reject-reason").value.toString().trim();reviewRow.innerHTML=".";const args={postid:postID,reason:rejectMessage||null},nextPostURL=await _ajax.default.call([{methodname:"mod_moodleoverflow_review_reject_post",args:args}])[0];let message=await(0,_str.get_string)("post_was_rejected","mod_moodleoverflow")+" ";message+=nextPostURL?``+await(0,_str.get_string)("jump_to_next_post_needing_review","mod_moodleoverflow")+"":await(0,_str.get_string)("there_are_no_posts_needing_review","mod_moodleoverflow"),reviewRow.innerHTML=message}else"reject-cancel"===action&&(reviewRow.innerHTML=".",reviewRow.innerHTML=await _templates.default.render("mod_moodleoverflow/review_buttons",{}))}},_ajax=_interopRequireDefault(_ajax),_prefetch=_interopRequireDefault(_prefetch),_templates=_interopRequireDefault(_templates)}); //# sourceMappingURL=reviewing.min.js.map \ No newline at end of file diff --git a/amd/build/reviewing.min.js.map b/amd/build/reviewing.min.js.map index d5002e736d..37e6a1b752 100644 --- a/amd/build/reviewing.min.js.map +++ b/amd/build/reviewing.min.js.map @@ -1 +1 @@ -{"version":3,"file":"reviewing.min.js","sources":["../src/reviewing.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Implements reviewing functionality\n *\n * @module mod_moodleoverflow/reviewing\n * @copyright 2022 Justus Dieckmann WWU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\nimport Prefetch from 'core/prefetch';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Init function.\n */\nexport function init() {\n Prefetch.prefetchTemplates(['mod_moodleoverflow/reject_post_form', 'mod_moodleoverflow/review_buttons']);\n Prefetch.prefetchStrings('mod_moodleoverflow',\n ['post_was_approved', 'jump_to_next_post_needing_review', 'there_are_no_posts_needing_review', 'post_was_rejected']);\n\n const root = document.getElementById('moodleoverflow-posts');\n root.onclick = async(e) => {\n const action = e.target.getAttribute('data-moodleoverflow-action');\n\n if (!action) {\n return;\n }\n\n const post = e.target.closest('*[data-moodleoverflow-postid]');\n const reviewRow = e.target.closest('.reviewrow');\n const postID = post.getAttribute('data-moodleoverflow-postid');\n\n if (action === 'approve') {\n reviewRow.innerHTML = '.';\n const nextPostURL = await Ajax.call([{\n methodname: 'mod_moodleoverflow_review_approve_post',\n args: {\n postid: postID,\n }\n }])[0];\n\n let message = await getString('post_was_approved', 'mod_moodleoverflow') + ' ';\n if (nextPostURL) {\n message += ``\n + await getString('jump_to_next_post_needing_review', 'mod_moodleoverflow')\n + \"\";\n } else {\n message += await getString('there_are_no_posts_needing_review', 'mod_moodleoverflow');\n }\n reviewRow.innerHTML = message;\n post.classList.remove(\"pendingreview\");\n } else if (action === 'reject') {\n reviewRow.innerHTML = '.';\n reviewRow.innerHTML = await Templates.render('mod_moodleoverflow/reject_post_form', {});\n } else if (action === 'reject-submit') {\n const rejectMessage = post.querySelector('textarea.reject-reason').value.toString().trim();\n reviewRow.innerHTML = '.';\n const args = {\n postid: postID,\n reason: rejectMessage ? rejectMessage : null\n };\n const nextPostURL = await Ajax.call([{\n methodname: 'mod_moodleoverflow_review_reject_post',\n args: args\n }])[0];\n\n let message = await getString('post_was_rejected', 'mod_moodleoverflow') + ' ';\n if (nextPostURL) {\n message += ``\n + await getString('jump_to_next_post_needing_review', 'mod_moodleoverflow')\n + \"\";\n } else {\n message += await getString('there_are_no_posts_needing_review', 'mod_moodleoverflow');\n }\n reviewRow.innerHTML = message;\n } else if (action === 'reject-cancel') {\n reviewRow.innerHTML = '.';\n reviewRow.innerHTML = await Templates.render('mod_moodleoverflow/review_buttons', {});\n }\n };\n}"],"names":["prefetchTemplates","prefetchStrings","document","getElementById","onclick","async","action","e","target","getAttribute","post","closest","reviewRow","postID","innerHTML","nextPostURL","Ajax","call","methodname","args","postid","message","classList","remove","Templates","render","rejectMessage","querySelector","value","toString","trim","reason"],"mappings":";;;;;;;wGA+BaA,kBAAkB,CAAC,sCAAuC,wDAC1DC,gBAAgB,qBACrB,CAAC,oBAAqB,mCAAoC,oCAAqC,sBAEtFC,SAASC,eAAe,wBAChCC,QAAUC,MAAAA,UACLC,OAASC,EAAEC,OAAOC,aAAa,kCAEhCH,oBAICI,KAAOH,EAAEC,OAAOG,QAAQ,iCACxBC,UAAYL,EAAEC,OAAOG,QAAQ,cAC7BE,OAASH,KAAKD,aAAa,iCAElB,YAAXH,OAAsB,CACtBM,UAAUE,UAAY,UAChBC,kBAAoBC,cAAKC,KAAK,CAAC,CACjCC,WAAY,yCACZC,KAAM,CACFC,OAAQP,WAEZ,OAEAQ,cAAgB,mBAAU,oBAAqB,sBAAwB,IAEvEA,SADAN,YACW,mBAAYA,wBACX,mBAAU,mCAAoC,sBACpD,aAEW,mBAAU,oCAAqC,sBAEpEH,UAAUE,UAAYO,QACtBX,KAAKY,UAAUC,OAAO,sBACnB,GAAe,WAAXjB,OACPM,UAAUE,UAAY,IACtBF,UAAUE,gBAAkBU,mBAAUC,OAAO,sCAAuC,SACjF,GAAe,kBAAXnB,OAA4B,OAC7BoB,cAAgBhB,KAAKiB,cAAc,0BAA0BC,MAAMC,WAAWC,OACpFlB,UAAUE,UAAY,UAChBK,KAAO,CACTC,OAAQP,OACRkB,OAAQL,eAAgC,MAEtCX,kBAAoBC,cAAKC,KAAK,CAAC,CACjCC,WAAY,wCACZC,KAAMA,QACN,OAEAE,cAAgB,mBAAU,oBAAqB,sBAAwB,IAEvEA,SADAN,YACW,mBAAYA,wBACX,mBAAU,mCAAoC,sBACpD,aAEW,mBAAU,oCAAqC,sBAEpEH,UAAUE,UAAYO,YACJ,kBAAXf,SACPM,UAAUE,UAAY,IACtBF,UAAUE,gBAAkBU,mBAAUC,OAAO,oCAAqC"} \ No newline at end of file +{"version":3,"file":"reviewing.min.js","sources":["../src/reviewing.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Implements reviewing functionality\n *\n * @module mod_moodleoverflow/reviewing\n * @copyright 2022 Justus Dieckmann WWU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\nimport Prefetch from 'core/prefetch';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Init function.\n */\nexport function init() {\n Prefetch.prefetchTemplates(['mod_moodleoverflow/reject_post_form', 'mod_moodleoverflow/review_buttons']);\n Prefetch.prefetchStrings('mod_moodleoverflow',\n ['post_was_approved',\n 'jump_to_next_post_needing_review',\n 'there_are_no_posts_needing_review',\n 'post_was_rejected']);\n\n const root = document.getElementById('moodleoverflow-posts');\n root.onclick = async(e) => {\n const action = e.target.getAttribute('data-moodleoverflow-action');\n\n if (!action) {\n return;\n }\n\n const post = e.target.closest('*[data-moodleoverflow-postid]');\n const reviewRow = e.target.closest('.reviewrow');\n const postID = post.getAttribute('data-moodleoverflow-postid');\n\n if (action === 'approve') {\n reviewRow.innerHTML = '.';\n const nextPostURL = await Ajax.call([{\n methodname: 'mod_moodleoverflow_review_approve_post',\n args: {\n postid: postID,\n }\n }])[0];\n\n let message = await getString('post_was_approved', 'mod_moodleoverflow') + ' ';\n if (nextPostURL) {\n message += ``\n + await getString('jump_to_next_post_needing_review', 'mod_moodleoverflow')\n + \"\";\n } else {\n message += await getString('there_are_no_posts_needing_review', 'mod_moodleoverflow');\n }\n reviewRow.innerHTML = message;\n post.classList.remove(\"pendingreview\");\n } else if (action === 'reject') {\n reviewRow.innerHTML = '.';\n reviewRow.innerHTML = await Templates.render('mod_moodleoverflow/reject_post_form', {});\n } else if (action === 'reject-submit') {\n const rejectMessage = post.querySelector('textarea.reject-reason').value.toString().trim();\n reviewRow.innerHTML = '.';\n const args = {\n postid: postID,\n reason: rejectMessage ? rejectMessage : null\n };\n const nextPostURL = await Ajax.call([{\n methodname: 'mod_moodleoverflow_review_reject_post',\n args: args\n }])[0];\n\n let message = await getString('post_was_rejected', 'mod_moodleoverflow') + ' ';\n if (nextPostURL) {\n message += ``\n + await getString('jump_to_next_post_needing_review', 'mod_moodleoverflow')\n + \"\";\n } else {\n message += await getString('there_are_no_posts_needing_review', 'mod_moodleoverflow');\n }\n reviewRow.innerHTML = message;\n } else if (action === 'reject-cancel') {\n reviewRow.innerHTML = '.';\n reviewRow.innerHTML = await Templates.render('mod_moodleoverflow/review_buttons', {});\n }\n };\n}"],"names":["_interopRequireDefault","e","__esModule","default","Prefetch","prefetchTemplates","prefetchStrings","document","getElementById","onclick","async","action","target","getAttribute","post","closest","reviewRow","postID","innerHTML","nextPostURL","Ajax","call","methodname","args","postid","message","getString","get_string","classList","remove","Templates","render","rejectMessage","querySelector","value","toString","trim","reason","_ajax","_prefetch","_templates"],"mappings":"6JAwBuC,SAAAA,uBAAAC,GAAAA,OAAAA,GAAAA,EAAAC,WAAAD,EAAAE,CAAAA,QAAAF,EAAA;;;;;;;2EAMhC,WACHG,UAAQD,QAACE,kBAAkB,CAAC,sCAAuC,sCACnED,UAAAA,QAASE,gBAAgB,qBACrB,CAAC,oBACM,mCACA,oCACA,sBAEEC,SAASC,eAAe,wBAChCC,QAAUC,UACX,MAAMC,OAASV,EAAEW,OAAOC,aAAa,8BAErC,IAAKF,OACD,OAGJ,MAAMG,KAAOb,EAAEW,OAAOG,QAAQ,iCACxBC,UAAYf,EAAEW,OAAOG,QAAQ,cAC7BE,OAASH,KAAKD,aAAa,8BAEjC,GAAe,YAAXF,OAAsB,CACtBK,UAAUE,UAAY,IACtB,MAAMC,kBAAoBC,cAAKC,KAAK,CAAC,CACjCC,WAAY,yCACZC,KAAM,CACFC,OAAQP,WAEZ,GAEJ,IAAIQ,cAAgB,EAAAC,KAAAA,YAAU,oBAAqB,sBAAwB,IAEvED,SADAN,YACW,YAAYA,sBACX,EAAAO,KAASC,YAAC,mCAAoC,sBACpD,aAEW,EAAAD,KAAAA,YAAU,oCAAqC,sBAEpEV,UAAUE,UAAYO,QACtBX,KAAKc,UAAUC,OAAO,gBAC1B,MAAO,GAAe,WAAXlB,OACPK,UAAUE,UAAY,IACtBF,UAAUE,gBAAkBY,WAAS3B,QAAC4B,OAAO,sCAAuC,CAAA,QACjF,GAAe,kBAAXpB,OAA4B,CACnC,MAAMqB,cAAgBlB,KAAKmB,cAAc,0BAA0BC,MAAMC,WAAWC,OACpFpB,UAAUE,UAAY,IACtB,MAAMK,KAAO,CACTC,OAAQP,OACRoB,OAAQL,eAAgC,MAEtCb,kBAAoBC,cAAKC,KAAK,CAAC,CACjCC,WAAY,wCACZC,KAAMA,QACN,GAEJ,IAAIE,cAAgB,EAAAC,KAAAA,YAAU,oBAAqB,sBAAwB,IAEvED,SADAN,YACW,YAAYA,sBACX,EAAAO,KAASC,YAAC,mCAAoC,sBACpD,aAEW,EAAAD,KAAAA,YAAU,oCAAqC,sBAEpEV,UAAUE,UAAYO,OAC1B,KAAsB,kBAAXd,SACPK,UAAUE,UAAY,IACtBF,UAAUE,gBAAkBY,WAAS3B,QAAC4B,OAAO,oCAAqC,CAAA,IAG9F,EA5EAO,MAAAtC,uBAAAsC,OACAC,UAAAvC,uBAAAuC,WACAC,WAAAxC,uBAAAwC,WA0EC"} \ No newline at end of file diff --git a/amd/build/warnmodechange.min.js b/amd/build/warnmodechange.min.js index 5042ba704b..605c6f767f 100644 --- a/amd/build/warnmodechange.min.js +++ b/amd/build/warnmodechange.min.js @@ -1,10 +1,10 @@ -define("mod_moodleoverflow/warnmodechange",["exports","core/str","core/notification","core/prefetch"],(function(_exports,_str,_notification,_prefetch){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("mod_moodleoverflow/warnmodechange",["exports","core/str","core/notification","core/prefetch"],function(_exports,_str,_notification,_prefetch){function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}} /** * Warns on changing the subscription mode. * * @module mod_moodleoverflow/warnmodechange * @copyright 2022 Justus Dieckmann WWU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(previousSetting){_prefetch.default.prefetchStrings("mod_moodleoverflow",["switchtooptional","switchtoauto"]),_prefetch.default.prefetchStrings("moodle",["confirm","cancel"]);const form=document.querySelector("form.mform"),select=document.getElementById("id_forcesubscribe");form.onsubmit=async e=>{const value=select.selectedOptions[0].value;value!=previousSetting&&1!=value&&3!=value&&(e.preventDefault(),await _notification.default.confirm(await(0,_str.get_string)("confirm"),await(0,_str.get_string)(0==value?"switchtooptional":"switchtoauto","mod_moodleoverflow"),await(0,_str.get_string)("confirm"),await(0,_str.get_string)("cancel"),(()=>{form.onsubmit=void 0,form.requestSubmit(e.submitter)}),void 0))}},_notification=_interopRequireDefault(_notification),_prefetch=_interopRequireDefault(_prefetch)})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(previousSetting){_prefetch.default.prefetchStrings("mod_moodleoverflow",["switchtooptional","switchtoauto"]),_prefetch.default.prefetchStrings("moodle",["confirm","cancel"]);const form=document.querySelector("form.mform"),select=document.getElementById("id_forcesubscribe");form.onsubmit=async e=>{const value=select.selectedOptions[0].value;value!=previousSetting&&1!=value&&3!=value&&(e.preventDefault(),await _notification.default.confirm(await(0,_str.get_string)("confirm"),await(0,_str.get_string)(0==value?"switchtooptional":"switchtoauto","mod_moodleoverflow"),await(0,_str.get_string)("confirm"),await(0,_str.get_string)("cancel"),()=>{form.onsubmit=void 0,form.requestSubmit(e.submitter)},void 0))}},_notification=_interopRequireDefault(_notification),_prefetch=_interopRequireDefault(_prefetch)}); //# sourceMappingURL=warnmodechange.min.js.map \ No newline at end of file diff --git a/amd/build/warnmodechange.min.js.map b/amd/build/warnmodechange.min.js.map index 70683e5540..1d84d0f41f 100644 --- a/amd/build/warnmodechange.min.js.map +++ b/amd/build/warnmodechange.min.js.map @@ -1 +1 @@ -{"version":3,"file":"warnmodechange.min.js","sources":["../src/warnmodechange.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Warns on changing the subscription mode.\n *\n * @module mod_moodleoverflow/warnmodechange\n * @copyright 2022 Justus Dieckmann WWU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {get_string as getString} from 'core/str';\nimport Notification from 'core/notification';\nimport Prefetch from 'core/prefetch';\n\n/**\n * Init function.\n * @param {string} previousSetting\n */\nexport function init(previousSetting) {\n Prefetch.prefetchStrings('mod_moodleoverflow', ['switchtooptional', 'switchtoauto']);\n Prefetch.prefetchStrings('moodle', ['confirm', 'cancel']);\n const form = document.querySelector('form.mform');\n const select = document.getElementById('id_forcesubscribe');\n form.onsubmit = async(e) => {\n const value = select.selectedOptions[0].value;\n if (value == previousSetting || value == 1 || value == 3) {\n return;\n }\n e.preventDefault();\n await Notification.confirm(\n await getString('confirm'),\n await getString(value == 0 ? 'switchtooptional' : 'switchtoauto', 'mod_moodleoverflow'),\n await getString('confirm'),\n await getString('cancel'),\n () => {\n // Prevent this listener from preventing the event again.\n form.onsubmit = undefined;\n form.requestSubmit(e.submitter);\n }, undefined);\n };\n}"],"names":["previousSetting","prefetchStrings","form","document","querySelector","select","getElementById","onsubmit","async","value","selectedOptions","e","preventDefault","Notification","confirm","undefined","requestSubmit","submitter"],"mappings":";;;;;;;oFA8BqBA,mCACRC,gBAAgB,qBAAsB,CAAC,mBAAoB,mCAC3DA,gBAAgB,SAAU,CAAC,UAAW,iBACzCC,KAAOC,SAASC,cAAc,cAC9BC,OAASF,SAASG,eAAe,qBACvCJ,KAAKK,SAAWC,MAAAA,UACNC,MAAQJ,OAAOK,gBAAgB,GAAGD,MACpCA,OAAST,iBAA4B,GAATS,OAAuB,GAATA,QAG9CE,EAAEC,uBACIC,sBAAaC,cACT,mBAAU,iBACV,mBAAmB,GAATL,MAAa,mBAAqB,eAAgB,4BAC5D,mBAAU,iBACV,mBAAU,WAChB,KAEIP,KAAKK,cAAWQ,EAChBb,KAAKc,cAAcL,EAAEM,kBACtBF"} \ No newline at end of file +{"version":3,"file":"warnmodechange.min.js","sources":["../src/warnmodechange.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Warns on changing the subscription mode.\n *\n * @module mod_moodleoverflow/warnmodechange\n * @copyright 2022 Justus Dieckmann WWU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {get_string as getString} from 'core/str';\nimport Notification from 'core/notification';\nimport Prefetch from 'core/prefetch';\n\n/**\n * Init function.\n * @param {string} previousSetting\n */\nexport function init(previousSetting) {\n Prefetch.prefetchStrings('mod_moodleoverflow', ['switchtooptional', 'switchtoauto']);\n Prefetch.prefetchStrings('moodle', ['confirm', 'cancel']);\n const form = document.querySelector('form.mform');\n const select = document.getElementById('id_forcesubscribe');\n form.onsubmit = async(e) => {\n const value = select.selectedOptions[0].value;\n if (value == previousSetting || value == 1 || value == 3) {\n return;\n }\n\n e.preventDefault();\n await Notification.confirm(\n await getString('confirm'),\n await getString(value == 0 ? 'switchtooptional' : 'switchtoauto', 'mod_moodleoverflow'),\n await getString('confirm'),\n await getString('cancel'),\n () => {\n // Prevent this listener from preventing the event again.\n form.onsubmit = undefined;\n form.requestSubmit(e.submitter);\n }, undefined);\n };\n}"],"names":["_interopRequireDefault","e","__esModule","default","previousSetting","Prefetch","prefetchStrings","form","document","querySelector","select","getElementById","onsubmit","async","value","selectedOptions","preventDefault","Notification","confirm","getString","undefined","requestSubmit","submitter","_notification","_prefetch"],"mappings":"sJAwBqC,SAAAA,uBAAAC,GAAAA,OAAAA,GAAAA,EAAAC,WAAAD,EAAAE,CAAAA,QAAAF,EAAA;;;;;;;2EAM9B,SAAcG,iBACjBC,UAAQF,QAACG,gBAAgB,qBAAsB,CAAC,mBAAoB,iBACpED,UAAQF,QAACG,gBAAgB,SAAU,CAAC,UAAW,WAC/C,MAAMC,KAAOC,SAASC,cAAc,cAC9BC,OAASF,SAASG,eAAe,qBACvCJ,KAAKK,SAAWC,UACZ,MAAMC,MAAQJ,OAAOK,gBAAgB,GAAGD,MACpCA,OAASV,iBAA4B,GAATU,OAAuB,GAATA,QAI9Cb,EAAEe,uBACIC,sBAAaC,cACT,EAAAC,iBAAU,iBACV,EAAAA,KAAAA,YAAmB,GAATL,MAAa,mBAAqB,eAAgB,4BAC5D,EAAAK,iBAAU,iBACV,EAAAA,KAAAA,YAAU,UAChB,KAEIZ,KAAKK,cAAWQ,EAChBb,KAAKc,cAAcpB,EAAEqB,iBACtBF,IAEf,EA9BAG,cAAAvB,uBAAAuB,eACAC,UAAAxB,uBAAAwB,UA6BC"} \ No newline at end of file diff --git a/amd/src/activityhelp.js b/amd/src/activityhelp.js index 601b6ad243..58427b30b4 100644 --- a/amd/src/activityhelp.js +++ b/amd/src/activityhelp.js @@ -21,8 +21,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - - const Selectors = { actions: { showHelpIcon: '[data-action="showhelpicon"]', @@ -30,7 +28,7 @@ const Selectors = { }; /** - * Function that shows the help string. + * Function that shows the help icon string. */ export const init = () => { document.addEventListener('click', event => { diff --git a/amd/src/rating.js b/amd/src/rating.js index 0f44f3ba77..259e2bc8c2 100644 --- a/amd/src/rating.js +++ b/amd/src/rating.js @@ -48,15 +48,19 @@ async function sendVote(postid, rating, userid) { ratingid: rating } }])[0]; + root.querySelectorAll(`[data-moodleoverflow-userreputation="${userid}"]`).forEach((i) => { i.textContent = response.raterreputation; }); + root.querySelectorAll(`[data-moodleoverflow-userreputation="${response.ownerid}"]`).forEach((i) => { i.textContent = response.ownerreputation; }); + root.querySelectorAll(`[data-moodleoverflow-postreputation="${postid}"]`).forEach((i) => { i.textContent = response.postrating; }); + return response; } @@ -149,7 +153,7 @@ export function init(userid, allowmultiplemarks) { /** * Function to change the String of the post data-action button. - * Only used if mulitplemarks are allowed. + * Only used if multiplemarks are allowed. * @param {string} htmlclass the class where the String is being updated * @param {string} action helpful or solved mark */ diff --git a/amd/src/reviewing.js b/amd/src/reviewing.js index 0525609f59..72213cbb0d 100644 --- a/amd/src/reviewing.js +++ b/amd/src/reviewing.js @@ -31,7 +31,10 @@ import {get_string as getString} from 'core/str'; export function init() { Prefetch.prefetchTemplates(['mod_moodleoverflow/reject_post_form', 'mod_moodleoverflow/review_buttons']); Prefetch.prefetchStrings('mod_moodleoverflow', - ['post_was_approved', 'jump_to_next_post_needing_review', 'there_are_no_posts_needing_review', 'post_was_rejected']); + ['post_was_approved', + 'jump_to_next_post_needing_review', + 'there_are_no_posts_needing_review', + 'post_was_rejected']); const root = document.getElementById('moodleoverflow-posts'); root.onclick = async(e) => { diff --git a/amd/src/warnmodechange.js b/amd/src/warnmodechange.js index 4d59b2dc2e..afb4cee3e9 100644 --- a/amd/src/warnmodechange.js +++ b/amd/src/warnmodechange.js @@ -38,6 +38,7 @@ export function init(previousSetting) { if (value == previousSetting || value == 1 || value == 3) { return; } + e.preventDefault(); await Notification.confirm( await getString('confirm'), diff --git a/backup/moodle2/backup_moodleoverflow_stepslib.php b/backup/moodle2/backup_moodleoverflow_stepslib.php index aa56787799..391f6754ef 100644 --- a/backup/moodle2/backup_moodleoverflow_stepslib.php +++ b/backup/moodle2/backup_moodleoverflow_stepslib.php @@ -39,14 +39,16 @@ class backup_moodleoverflow_activity_structure_step extends backup_activity_stru * @return backup_nested_element */ protected function define_structure() { + // To know if we are including userinfo. $userinfo = $this->get_setting_value('userinfo'); // Define the root element describing the moodleoverflow instance. $moodleoverflow = new backup_nested_element('moodleoverflow', ['id'], [ - 'name', 'intro', 'introformat', 'maxbytes', 'maxattachments', 'timecreated', 'timemodified', 'forcesubscribe', - 'trackingtype', 'ratingpreference', 'coursewidereputation', 'allowrating', 'allowreputation', 'allownegativereputation', - 'grademaxgrade', 'gradescalefactor', 'gradecat', 'anonymous', 'allowmultiplemarks', ]); + 'name', 'intro', 'introformat', 'maxbytes', 'maxattachments', 'timecreated', 'timemodified', + 'forcesubscribe', 'trackingtype', 'ratingpreference', 'coursewidereputation', 'allowrating', + 'allowreputation', 'allownegativereputation', 'grademaxgrade', 'gradescalefactor', 'gradecat', + 'anonymous', 'allowmultiplemarks', 'la_starttime', 'la_endtime', ]); // Define each element separated. $discussions = new backup_nested_element('discussions'); @@ -54,20 +56,24 @@ protected function define_structure() { 'name', 'firstpost', 'userid', 'timestart', 'timemodified', 'usermodified', ]); $posts = new backup_nested_element('posts'); - $post = new backup_nested_element('post', ['id'], ['parent', 'userid', 'created', 'modified', 'message', - 'messageformat', 'attachment', 'mailed', 'reviewed', 'timereviewed', ]); + $post = new backup_nested_element('post', ['id'], [ + 'parent', 'userid', 'created', 'modified', + 'message', 'messageformat', 'attachment', 'mailed', 'reviewed', 'timereviewed', ]); $ratings = new backup_nested_element('ratings'); - $rating = new backup_nested_element('rating', ['id'], ['userid', 'rating', 'firstrated', 'lastchanged']); + $rating = new backup_nested_element('rating', ['id'], [ + 'userid', 'rating', 'firstrated', 'lastchanged', ]); $discussionsubs = new backup_nested_element('discuss_subs'); - $discussionsub = new backup_nested_element('discuss_sub', ['id'], ['userid', 'preference']); + $discussionsub = new backup_nested_element('discuss_sub', ['id'], [ + 'userid', 'preference', ]); $subscriptions = new backup_nested_element('subscriptions'); $subscription = new backup_nested_element('subscription', ['id'], ['userid']); $readposts = new backup_nested_element('readposts'); - $read = new backup_nested_element('read', ['id'], ['userid', 'discussionid', 'postid', 'firstread', 'lastread']); + $read = new backup_nested_element('read', ['id'], [ + 'userid', 'discussionid', 'postid', 'firstread', 'lastread', ]); $grades = new backup_nested_element('grades'); $grade = new backup_nested_element('grade', ['id'], ['userid', 'grade']); @@ -94,9 +100,6 @@ protected function define_structure() { $moodleoverflow->add_child($readposts); $readposts->add_child($read); - $moodleoverflow->add_child($grades); - $grades->add_child($grade); - $moodleoverflow->add_child($tracking); $tracking->add_child($track); diff --git a/classes/discussion/discussion.php b/classes/discussion/discussion.php new file mode 100644 index 0000000000..68b2d7a03d --- /dev/null +++ b/classes/discussion/discussion.php @@ -0,0 +1,624 @@ +. + +/** + * Class for working with posts + * + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_moodleoverflow\discussion; + + +// Import namespace from the locallib, needs a check later which namespaces are really needed. +use mod_moodleoverflow\anonymous; + +// Important namespaces. +use mod_moodleoverflow\readtracking; +use mod_moodleoverflow\review; +use mod_moodleoverflow\post\post; +use mod_moodleoverflow\capabilities; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/mod/moodleoverflow/locallib.php'); + +/** + * Class that represents a discussion. A discussion administrates the posts and has one parent post, that started the discussion. + * + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * Please be careful with functions that delete, add or edit posts and discussions. + * Security checks for these functions were done in the post_control class and these functions should only be accessed that way. + * Accessing these functions directly without the checks from the post control could lead to serious errors. + */ +class discussion { + + /** @var int The discussion ID */ + private $id; + + /** @var int The course ID where the discussion is located */ + private $course; + + /** @var int The moodleoverflow ID where the discussion is located*/ + private $moodleoverflow; + + /** @var string The title of the discussion, the titel of the parent post*/ + public $name; + + /** @var int The id of the parent/first post*/ + private $firstpost; + + /** @var int The user ID who started the discussion */ + private $userid; + + /** @var int Unix-timestamp of modification */ + public $timemodified; + + /** @var int Unix-timestamp of discussion creation */ + public $timestart; + + /** @var int the user ID who modified the discussion */ + public $usermodified; + + // Not Database-related attributes. + + /** @var array an Array of posts that belong to this discussion */ + public $posts; + + /** @var bool a variable for checking if this instance has all its posts */ + public $postsbuild; + + /** @var object The moodleoverflow object where the discussion is located */ + public $moodleoverflowobject; + + /** @var object The course module object */ + public $cmobject; + + // Constructors and other builders. + + /** + * Constructor to build a new discussion. + * @param int $id The Discussion ID. + * @param int $course The course ID. + * @param int $moodleoverflow The moodleoverflow ID. + * @param char $name Discussion Title. + * @param int $firstpost . + * @param int $userid The course ID. + * @param int $timemodified The course ID. + * @param int $timestart The course ID. + * @param int $usermodified The course ID. + */ + public function __construct($id, $course, $moodleoverflow, $name, $firstpost, + $userid, $timemodified, $timestart, $usermodified) { + $this->id = $id; + $this->course = $course; + $this->moodleoverflow = $moodleoverflow; + $this->name = $name; + $this->firstpost = $firstpost; + $this->userid = $userid; + $this->timemodified = $timemodified; + $this->timestart = $timestart; + $this->usermodified = $usermodified; + $this->posts = []; + $this->postsbuild = false; + } + + /** + * Builds a Discussion from a DB record. + * + * @param object $record Data object. + * @return object discussion instance + */ + public static function from_record($record) { + $id = null; + if (object_property_exists($record, 'id') && $record->id) { + $id = $record->id; + } + + $course = 0; + if (object_property_exists($record, 'course') && $record->course) { + $course = $record->course; + } + + $moodleoverflow = 0; + if (object_property_exists($record, 'moodleoverflow') && $record->moodleoverflow) { + $moodleoverflow = $record->moodleoverflow; + } + + $name = ''; + if (object_property_exists($record, 'name') && $record->name) { + $name = $record->name; + } + + $firstpost = 0; + if (object_property_exists($record, 'firstpost') && $record->firstpost) { + $firstpost = $record->firstpost; + } + + $userid = 0; + if (object_property_exists($record, 'userid') && $record->userid) { + $userid = $record->userid; + } + + $timemodified = 0; + if (object_property_exists($record, 'timemodified') && $record->timemodified) { + $timemodified = $record->timemodified; + } + + $timestart = 0; + if (object_property_exists($record, 'timestart') && $record->timestart) { + $timestart = $record->timestart; + } + + $usermodified = 0; + if (object_property_exists($record, 'usermodified') && $record->usermodified) { + $usermodified = $record->usermodified; + } + + $instance = new self($id, $course, $moodleoverflow, $name, $firstpost, $userid, $timemodified, $timestart, $usermodified); + + // Get all the posts so that the instance can work with it. + $instance->moodleoverflow_get_discussion_posts(); + + return $instance; + } + + /** + * Function to build a new discussion without specifying the Discussion ID. + * @param int $course The course ID. + * @param int $moodleoverflow The moodleoverflow ID. + * @param char $name Discussion Title. + * @param int $firstpost . + * @param int $userid The course ID. + * @param int $timemodified The course ID. + * @param int $timestart The course ID. + * @param int $usermodified The course ID. + * + * @return object discussion object without id. + */ + public static function construct_without_id($course, $moodleoverflow, $name, $firstpost, + $userid, $timemodified, $timestart, $usermodified) { + $id = null; + $instance = new self($id, $course, $moodleoverflow, $name, $firstpost, $userid, $timemodified, $timestart, $usermodified); + return $instance; + } + + // Discussion Functions. + + /** + * Adds a new Discussion with a post. + * + * @param object $prepost The prepost object from the post_control. Has information about the post and other important stuff. + */ + public function moodleoverflow_add_discussion($prepost) { + global $DB; + + // Add the discussion to the Database. + $this->id = $DB->insert_record('moodleoverflow_discussions', $this->build_db_object()); + + // Create the first/parent post for the new discussion and add it do the DB. + $post = post::construct_without_id($this->id, 0, $prepost->userid, $prepost->timenow, $prepost->timenow, $prepost->message, + $prepost->messageformat, "", 0, $prepost->reviewed, null, $prepost->formattachments); + // Add it to the DB and save the id of the first/parent post. + $this->firstpost = $post->moodleoverflow_add_new_post(); + + // Save the id of the first/parent post in the DB. + $DB->set_field('moodleoverflow_discussions', 'firstpost', $this->firstpost, ['id' => $this->id]); + + // Add the parent post to the $posts array. + $this->posts[$this->firstpost] = $post; + $this->postsbuild = true; + + // Trigger event. + $params = [ + 'context' => $prepost->modulecontext, + 'objectid' => $this->id, + ]; + // LEARNWEB-TODO: check if the event functions. + $event = \mod_moodleoverflow\event\discussion_viewed::create($params); + $event->trigger(); + + // Return the id of the discussion. + return $this->id; + } + + /** + * Delete a discussion with all of it's posts + * @param object $prepost Information about the post from the post_control + * @return bool Wether deletion was successful of not + */ + public function moodleoverflow_delete_discussion($prepost) { + global $DB; + $this->existence_check(); + $this->posts_check(); + + // Delete a discussion with all of it's posts. + // In case something does not work we throw the error as it should be known that something went ... terribly wrong. + // All DB transactions are rolled back. + try { + $transaction = $DB->start_delegated_transaction(); + + // Delete every post of this discussion. + foreach ($this->posts as $post) { + $post->moodleoverflow_delete_post(false); + } + + // Delete the read-records for the discussion. + readtracking::moodleoverflow_delete_read_records(-1, -1, $this->id); + + // Remove the subscriptions for the discussion. + $DB->delete_records('moodleoverflow_discuss_subs', ['discussion' => $this->id]); + + // Delete the discussion from the database. + $DB->delete_records('moodleoverflow_discussions', ['id' => $this->id]); + + // Trigger the discussion deleted event. + $params = [ + 'objectid' => $this->id, + 'context' => $prepost->modulecontext, + ]; + + $event = \mod_moodleoverflow\event\discussion_deleted::create($params); + $event->trigger(); + + // Set the id of this instance to null, so that working with it is not possible anymore. + $this->id = null; + + // The discussion has been deleted. + $transaction->allow_commit(); + return true; + + } catch (Exception $e) { + $transaction->rollback($e); + } + + // Deleting the discussion has failed. + return false; + } + + /** + * Adds a new post to this discussion and the DB. + * + * @param object $prepost The prepost object from the post_control. Has Information about the post and other important stuff. + */ + public function moodleoverflow_add_post_to_discussion($prepost) { + global $DB; + $this->existence_check(); + $this->posts_check(); + + // Create the post that will be added to the new discussion. + $post = post::construct_without_id($this->id, $prepost->parentid, $prepost->userid, $prepost->timenow, $prepost->timenow, + $prepost->message, $prepost->messageformat, "", 0, $prepost->reviewed, null, + $prepost->formattachments); + // Add the post to the DB. + $postid = $post->moodleoverflow_add_new_post(); + + // Add the post to the $posts array and update the timemodified in the DB. + $this->posts[$postid] = $post; + $this->timemodified = $prepost->timenow; + $this->usermodified = $prepost->userid; + $DB->update_record('moodleoverflow_discussions', $this->build_db_object()); + + // Return the id of the added post. + return $postid; + } + + /** + * Deletes a post that is in this discussion from the DB. + * @param object $prepost The prepost object from the post_control. Has Information about the post and other important stuff. + * @return bool Wether the deletion was possible + * @throws moodle_exception if post is not in this discussion or something failed. + */ + public function moodleoverflow_delete_post_from_discussion($prepost) { + $this->existence_check(); + $this->posts_check(); + + // Check if the posts exists in this discussion. + $this->post_exists_check($prepost->postid); + + // Access the post and delete it. + $post = $this->posts[$prepost->postid]; + if (!$post->moodleoverflow_delete_post($prepost->deletechildren)) { + // Deletion failed. + return false; + } + + // Check for the new last post of the discussion. + $this->moodleoverflow_discussion_adapt_to_last_post(); + + // Delete the post from the post array. + unset($this->posts[$prepost->postid]); + + return true; + } + + /** + * Edits the message of a post from this discussion. + * @param object $prepost The prepost object from the post_control. Has Information about the post and other important stuff. + */ + public function moodleoverflow_edit_post_from_discussion($prepost) { + global $DB; + $this->existence_check(); + $this->posts_check(); + + // Check if the posts exists in this discussion. + $this->post_exists_check($prepost->postid); + + // Access the post. + $post = $this->posts[$prepost->postid]; + + // If the post is the firstpost, then update the name of this discussion and the post. If not, only update the post. + if ($prepost->postid == array_key_first($this->posts)) { + $this->name = $prepost->subject; + $this->usermodified = $prepost->userid; + $this->timemodified = $prepost->timenow; + $DB->update_record('moodleoverflow_discussions', $this->build_db_object()); + } + $post->moodleoverflow_edit_post($prepost->timenow, $prepost->message, $prepost->messageformat, $prepost->formattachments); + + // The post has been edited successfully. + return true; + } + + /** + * This Function checks, what the last added or edited post is. If it changed by a delete function, + * the timemodified and the usermodified need to be adapted to the last added or edited post. + * + * @return bool true if the DB needed to be adapted. false if it didn't change. + */ + public function moodleoverflow_discussion_adapt_to_last_post() { + global $DB; + $this->existence_check(); + + // Find the last reviewed post of the discussion (even if the user has review capability, because it's written to DB). + $sql = 'SELECT * + FROM {moodleoverflow_posts} + WHERE discussion = ' . $this->id . + ' AND reviewed = 1 + AND modified = (SELECT MAX(modified) as modified + FROM {moodleoverflow_posts} + WHERE discussion = ' . $this->id . ');'; + $record = $DB->get_record_sql($sql); + $lastpost = post::from_record($record); + + // Check if the last post changed. If it changed, then update the DB-record of this discussion. + if ($lastpost->modified != $this->timemodified || $lastpost->get_userid() != $this->usermodified) { + $this->timemodified = $lastpost->modified; + $this->usermodified = $lastpost->get_userid(); + $DB->update_record('moodleoverflow_discussions', $this->build_db_object()); + + // Return that the discussion needed an update. + return true; + } + + // Return that the discussion didn't need an update. + return false; + } + + // Getter. + + /** + * Getter for the post ID + * @return int $this->id The post ID. + */ + public function get_id() { + $this->existence_check(); + return $this->id; + } + + /** + * Getter for the courseid + * @return int $this->course The ID of the course where the discussion is located. + */ + public function get_courseid() { + $this->existence_check(); + return $this->course; + } + + /** + * Getter for the moodleoverflowid + * @return int $this->moodleoverflow The ID of the moodleoverflow where the discussion is located. + */ + public function get_moodleoverflowid() { + $this->existence_check(); + return $this->moodleoverflow; + } + + /** + * Getter for the firstpostid + * @return int $this->firstpost The ID of the first post. + */ + public function get_firstpostid() { + $this->existence_check(); + return $this->firstpost; + } + + /** + * Getter for the userid + * @return int $this->userid The ID of the user who wrote the first post. + */ + public function get_userid() { + $this->existence_check(); + return $this->userid; + } + + /** + * Returns the ratings from this discussion. + * @return array of votings + */ + public function moodleoverflow_get_discussion_ratings() { + $this->existence_check(); + $this->posts_check(); + + $discussionratings = \mod_moodleoverflow\ratings::moodleoverflow_get_ratings_by_discussion($this->id); + return $discussionratings; + } + + /** + * Get all posts from this Discussion. + * The first/parent post is on the first position in the array. + * + * @return array $posts Array ob posts objects + */ + public function moodleoverflow_get_discussion_posts() { + global $DB; + $this->existence_check(); + + // Check if the posts array are build yet. If not, build it. + if (!$this->postsbuild) { + // Get the posts from the DB. Get the parent post first. + $firstpostsql = 'SELECT * FROM {moodleoverflow_posts} posts + WHERE discussion = ' . $this->id . ' AND parent = 0;'; + $otherpostssql = 'SELECT * FROM {moodleoverflow_posts} posts + WHERE discussion = ' . $this->id . ' AND parent != 0;'; + $firstpostrecord = $DB->get_record_sql($firstpostsql); + $otherpostsrecord = $DB->get_records_sql($otherpostssql); + + // Add the first/parent post to the array, then add the other posts. + $firstpost = post::from_record($firstpostrecord); + $this->posts[$firstpost->get_id()] = $firstpost; + + foreach ($otherpostsrecord as $postrecord) { + $post = post::from_record($postrecord); + $this->posts[$post->get_id()] = $post; + } + + // Now the posts are built. + $this->postsbuild = true; + } + + // Return the posts array. + return $this->posts; + } + + + /** + * Returns the moodleoverflowobject + * + * @return object $moodleoverflowobject + */ + public function get_moodleoverflow() { + global $DB; + $this->existence_check(); + + if (empty($this->moodleoverflowobject)) { + $this->moodleoverflowobject = $DB->get_records('moodleoverflow', ['id' => $this->moodleoverflow]); + } + + return $this->moodleoverflowobject; + } + + /** + * Returns the coursemodule + * + * @return object $cmobject + */ + public function get_coursemodule() { + global $DB; + $this->existence_check(); + + if (empty($this->cmobject)) { + if (!$this->cmobject = $DB->get_coursemodule_from_instance('moodleoverflow', $this->get_moodleoverflow()->id, + $this->get_moodleoverflow()->course)) { + throw new \moodle_exception('invalidcoursemodule'); + } + } + + return $this->cmobject; + } + + /** + * This getter works as an help function in case another file/function needs the db-object of this instance (as the function + * is not adapted/refactored to the new way of working with discussion). + * @return object + */ + public function get_db_object() { + $this->existence_check(); + return $this->build_db_object(); + } + + // Helper functions. + + /** + * Builds an object from this instance that has only DB-relevant attributes. + * As this is an private function, it doesn't need an existence check. + * @return object $dbobject + */ + private function build_db_object() { + $dbobject = new \stdClass(); + $dbobject->id = $this->id; + $dbobject->course = $this->course; + $dbobject->moodleoverflow = $this->moodleoverflow; + $dbobject->name = $this->name; + $dbobject->firstpost = $this->firstpost; + $dbobject->userid = $this->userid; + $dbobject->timemodified = $this->timemodified; + $dbobject->timestart = $this->timestart; + $dbobject->usermodified = $this->usermodified; + + return $dbobject; + } + + // Security. + + /** + * Makes sure that the instance exists in the database. Every function in this class requires this check + * (except the function that adds the discussion to the database) + * + * @return true + * @throws moodle_exception + */ + private function existence_check() { + if (empty($this->id) || $this->id == false || $this->id == null) { + throw new \moodle_exception('noexistingdiscussion', 'moodleoverflow'); + } + return true; + } + + /** + * Makes sure that the instance knows all of its posts (That all posts of the db are in the local array). + * Not all functions need this check. + * @return true + * @throws moodle_exception + */ + private function posts_check() { + if (!$this->postsbuild) { + throw new \moodle_exception('notallpostsavailable', 'moodleoverflow'); + } + return true; + } + + /** + * Check, if certain posts really exists in this discussion. + * + * @param int $postid The ID of the post that is being checked. + * @return true + * @throws moodle_exception; + */ + private function post_exists_check($postid) { + if (!$this->posts[$postid]) { + throw new \moodle_exception('postnotpartofdiscussion', 'moodleoverflow'); + } + + return true; + } +} diff --git a/classes/output/helpicon.php b/classes/output/helpicon.php new file mode 100644 index 0000000000..b1867f850f --- /dev/null +++ b/classes/output/helpicon.php @@ -0,0 +1,72 @@ +. + +/** + * Use of the Helpicon from Moodle core. + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_moodleoverflow\output; + +/** + * Builds a Helpicon, that shows a String when hovering over it. + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class helpicon { + + /** @var object The Helpicon*/ + private $helpobject; + + /** + * Builds a Helpicon and stores it in helpobject. + * + * @param string $htmlclass The classname in which the icon will be. + * @param string $content A string that shows the information that the icon has. + */ + public function __construct($htmlclass, $content) { + global $CFG; + $iconurl = $CFG->wwwroot . '/pix/a/help.svg'; + $iconstyle = ['style' => + 'max-width: 20px; max-height: 20px; margin: 0; padding: 0; box-sizing: content-box; margin-right: .5rem;']; + $icon = \html_writer::img($iconurl, $content, $iconstyle); + + $class = $htmlclass; + $iconattributes = ['role' => 'button', + 'style' => 'display: inline;', + 'data-container' => 'body', + 'data-toggle' => 'popover', + 'data-placement' => 'right', + 'data-action' => 'showhelpicon', + 'data-html' => 'true', + 'data-trigger' => 'focus', + 'tabindex' => '0', + 'data-content' => '

' . $content . '

', ]; + $this->helpobject = \html_writer::span($icon, $class, $iconattributes); + } + + /** + * Returns the Helpicon, so that it can be used. + * + * @return object The Helpicon + */ + public function get_helpicon() { + return $this->helpobject; + } +} diff --git a/classes/output/moodleoverflow_email.php b/classes/output/moodleoverflow_email.php index 0dcd61b8bc..27865bb49c 100644 --- a/classes/output/moodleoverflow_email.php +++ b/classes/output/moodleoverflow_email.php @@ -188,7 +188,7 @@ protected function export_for_template_text(\mod_moodleoverflow_renderer $render * * @param \mod_moodleoverflow_renderer $renderer The render to be used for formatting the message and attachments * - * @return stdClass Data ready for use in a mustache template + * @return array Data ready for use in a mustache template */ protected function export_for_template_html(\mod_moodleoverflow_renderer $renderer) { return [ @@ -358,7 +358,7 @@ public function get_discussionname() { * @return string */ public function get_author_fullname() { - if (anonymous::is_post_anonymous($this->discussion, $this->moodleoverflow, $this->author->id)) { + if ($this->author->anonymous) { return get_string('privacy:anonym_user_name', 'mod_moodleoverflow'); } else { return fullname($this->author, $this->viewfullnames); @@ -521,7 +521,7 @@ public function get_moodleoverflowviewlink() { * @return string */ public function get_authorlink() { - if (anonymous::is_post_anonymous($this->discussion, $this->moodleoverflow, $this->author->id)) { + if ($this->author->anonymous) { return null; } @@ -542,7 +542,7 @@ public function get_authorlink() { */ public function get_author_picture() { global $OUTPUT; - if (anonymous::is_post_anonymous($this->discussion, $this->moodleoverflow, $this->author->id)) { + if ($this->author->anonymous) { return ''; } @@ -555,7 +555,7 @@ public function get_author_picture() { * @return string */ public function get_group_picture() { - if (anonymous::is_post_anonymous($this->discussion, $this->moodleoverflow, $this->author->id)) { + if ($this->author->anonymous) { return ''; } diff --git a/classes/post/post.php b/classes/post/post.php new file mode 100644 index 0000000000..b5b022e1be --- /dev/null +++ b/classes/post/post.php @@ -0,0 +1,718 @@ +. + +/** + * Class for working with posts + * + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +namespace mod_moodleoverflow\post; + +// Import namespace from the locallib, needs a check later which namespaces are really needed. +use mod_moodleoverflow\anonymous; +use mod_moodleoverflow\capabilities; +use mod_moodleoverflow\review; +use mod_moodleoverflow\readtracking; +use mod_moodleoverflow\discussion\discussion; +use moodle_exception; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/mod/moodleoverflow/locallib.php'); + +/** + * Class that represents a post. + * + * Please be careful with functions that delete, add or edit posts. + * Security checks for these functions were done in the post_control class and these functions should only be accessed that way. + * Accessing these functions directly without the checks from the post_control could lead to serious errors. + * + * Most of the functions in this class are called by moodleoverflow/classes/discussion/discussion.php . The discussion class + * manages posts in a moodleoverflow and works like a toplevel class for the post class. If you want to manipulate + * (delete, add, edit) posts, please call the functions from the discussion class. To read and obtain information about posts + * you are free to choose. + * + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class post { + + // Attributes. The most important attributes are private and can only be changed by internal functions. + // Other attributes can be accessed directly. + + /** @var int The post ID */ + private $id; + + /** @var int The corresponding discussion ID */ + private $discussion; + + /** @var int The parent post ID */ + private $parent; + + /** @var int The ID of the User who wrote the post */ + private $userid; + + /** @var int Creation timestamp */ + public $created; + + /** @var int Modification timestamp */ + public $modified; + + /** @var string The message (content) of the post */ + public $message; + + /** @var int The message format*/ + public $messageformat; + + /** @var string Attachment of the post */ + public $attachment; + + /** @var int Mailed status*/ + public $mailed; + + /** @var int Review status */ + public $reviewed; + + /** @var int The time where the post was reviewed*/ + public $timereviewed; + + // Not database related functions. + + /** @var int This variable is optional, it contains important information for the add_attachment function */ + public $formattachments; + + /** @var string The subject/title of the Discussion */ + public $subject; + + /** @var object The discussion where the post is located */ + public $discussionobject; + + /** @var object The Moodleoverflow where the post is located*/ + public $moodleoverflowobject; + + /** @var object The course module object */ + public $cmobject; + + /** @var object The parent post of an answerpost */ + public $parentpost; + + // Constructors and other builders. + + /** + * Constructor to make a new post. + * @param int $id The post ID. + * @param int $discussion The discussion ID. + * @param int $parent The parent post ID. + * @param int $userid The user ID that created the post. + * @param int $created Creation timestamp + * @param int $modified Modification timestamp + * @param string $message The message (content) of the post + * @param int $messageformat The message format + * @param char $attachment Attachment of the post + * @param int $mailed Mailed status + * @param int $reviewed Review status + * @param int $timereviewed The time where the post was reviewed + * @param object $formattachments Information about attachments of the post_form + */ + public function __construct($id, $discussion, $parent, $userid, $created, $modified, $message, + $messageformat, $attachment, $mailed, $reviewed, $timereviewed, $formattachments = false) { + $this->id = $id; + $this->discussion = $discussion; + $this->parent = $parent; + $this->userid = $userid; + $this->created = $created; + $this->modified = $modified; + $this->message = $message; + $this->messageformat = $messageformat; + $this->attachment = $attachment; + $this->mailed = $mailed; + $this->reviewed = $reviewed; + $this->timereviewed = $timereviewed; + $this->formattachments = $formattachments; + } + + /** + * Builds a Post from a DB record. + * Look up database structure for standard values. + * @param object $record Data object. + * @return object post instance + */ + public static function from_record($record) { + $id = null; + if (object_property_exists($record, 'id') && $record->id) { + $id = $record->id; + } + + $discussion = 0; + if (object_property_exists($record, 'discussion') && $record->discussion) { + $discussion = $record->discussion; + } + + $parent = 0; + if (object_property_exists($record, 'parent') && $record->parent) { + $parent = $record->parent; + } + + $userid = 0; + if (object_property_exists($record, 'userid') && $record->userid) { + $userid = $record->userid; + } + + $created = 0; + if (object_property_exists($record, 'created') && $record->created) { + $created = $record->created; + } + + $modified = 0; + if (object_property_exists($record, 'modified') && $record->modified) { + $modified = $record->modified; + } + + $message = ''; + if (object_property_exists($record, 'message') && $record->message) { + $message = $record->message; + } + + $messageformat = 0; + if (object_property_exists($record, 'messageformat') && $record->messageformat) { + $messageformat = $record->messageformat; + } + + $attachment = ''; + if (object_property_exists($record, 'attachment') && $record->attachment) { + $attachment = $record->attachment; + } + + $mailed = 0; + if (object_property_exists($record, 'mailed') && $record->mailed) { + $mailed = $record->mailed; + } + + $reviewed = 1; + if (object_property_exists($record, 'reviewed') && $record->reviewed) { + $reviewed = $record->reviewed; + } + + $timereviewed = null; + if (object_property_exists($record, 'timereviewed') && $record->timereviewed) { + $timereviewed = $record->timereviewed; + } + + return new self($id, $discussion, $parent, $userid, $created, $modified, $message, $messageformat, $attachment, $mailed, + $reviewed, $timereviewed); + } + + /** + * Function to make a new post without specifying the Post ID. + * + * @param int $discussion The discussion ID. + * @param int $parent The parent post ID. + * @param int $userid The user ID that created the post. + * @param int $created Creation timestamp + * @param int $modified Modification timestamp + * @param string $message The message (content) of the post + * @param int $messageformat The message format + * @param char $attachment Attachment of the post + * @param int $mailed Mailed status + * @param int $reviewed Review status + * @param int $timereviewed The time where the post was reviewed + * @param object $formattachments Information about attachments from the post_form + * + * @return object post object without id + */ + public static function construct_without_id($discussion, $parent, $userid, $created, $modified, $message, + $messageformat, $attachment, $mailed, $reviewed, $timereviewed, $formattachments = false) { + $id = null; + return new self($id, $discussion, $parent, $userid, $created, $modified, $message, $messageformat, $attachment, $mailed, + $reviewed, $timereviewed, $formattachments); + } + + // Post Functions. + + /** + * Adds a new post in an existing discussion. + * @return bool|int The Id of the post if operation was successful + * @throws coding_exception + * @throws dml_exception + */ + public function moodleoverflow_add_new_post() { + global $USER, $DB; + + // Add post to the database. + $this->id = $DB->insert_record('moodleoverflow_posts', $this->build_db_object()); + $this->moodleoverflow_add_attachment($this, $this->get_moodleoverflow(), $this->get_coursemodule()); + + if ($this->reviewed) { + // Update the discussion. + $DB->set_field('moodleoverflow_discussions', 'timemodified', $this->modified, ['id' => $this->discussion]); + $DB->set_field('moodleoverflow_discussions', 'usermodified', $this->userid, ['id' => $this->discussion]); + } + + // Mark the created post as read if the user is tracking the discussion. + $cantrack = readtracking::moodleoverflow_can_track_moodleoverflows($this->get_moodleoverflow()); + $istracked = readtracking::moodleoverflow_is_tracked($this->get_moodleoverflow()); + if ($cantrack && $istracked) { + // Please be aware that in future the use of get_db_object() should be replaced with only $this, + // as the readtracking class should be refactored with the new way of working with posts. + readtracking::moodleoverflow_mark_post_read($this->userid, $this->get_db_object()); + } + + // Return the id of the created post. + return $this->id; + } + + /** + * Deletes a single moodleoverflow post. + * + * @param bool $deletechildren The child posts + * + * @return bool Whether the deletion was successful or not + */ + public function moodleoverflow_delete_post($deletechildren) { + global $DB, $USER; + $this->existence_check(); + + // Iterate through all children and delete them. + // In case something does not work we throw the error as it should be known that something went ... terribly wrong. + // All DB transactions are rolled back. + try { + $transaction = $DB->start_delegated_transaction(); + + // Get the coursemoduleid for later use. + $coursemoduleid = $this->get_coursemodule()->id; + $childposts = $this->moodleoverflow_get_childposts(); + if ($deletechildren && $childposts) { + foreach ($childposts as $childpost) { + $child = $this->from_record($childpost); + $child->moodleoverflow_delete_post($deletechildren); + } + } + + // Delete the ratings. + $DB->delete_records('moodleoverflow_ratings', ['postid' => $this->id]); + + // Delete the post. + if ($DB->delete_records('moodleoverflow_posts', ['id' => $this->id])) { + // Delete the read records. + readtracking::moodleoverflow_delete_read_records(-1, $this->id); + + // Delete the attachments. + $fs = get_file_storage(); + $context = \context_module::instance($coursemoduleid); + $attachments = $fs->get_area_files($context->id, 'mod_moodleoverflow', 'attachment', + $this->id, "filename", true); + foreach ($attachments as $attachment) { + // Get file. + $file = $fs->get_file($context->id, 'mod_moodleoverflow', 'attachment', $this->id, + $attachment->get_filepath(), $attachment->get_filename()); + // Delete it if it exists. + if ($file) { + $file->delete(); + } + } + + // Trigger the post deletion event. + $params = [ + 'context' => $context, + 'objectid' => $this->id, + 'other' => [ + 'discussionid' => $this->discussion, + 'moodleoverflowid' => $this->get_moodleoverflow()->id, + ], + ]; + if ($this->userid !== $USER->id) { + $params['relateduserid'] = $this->userid; + } + $event = \mod_moodleoverflow\event\post_deleted::create($params); + $event->trigger(); + + // Set the id of this instance to null, so that working with it is not possible anymore. + $this->id = null; + + // The post has been deleted. + $transaction->allow_commit(); + return true; + } + } catch (Exception $e) { + $transaction->rollback($e); + } + + // Deleting the post failed. + return false; + } + + /** + * Edits the message from this instance. + * @param int $time The time the post was modified (given from the discussion class). + * @param string $postmessage The new message + * @param int $messageformat + * @param object $formattachments Information about attachments from the post_form + * + * @return true if the post has been edited successfully + */ + public function moodleoverflow_edit_post($time, $postmessage, $messageformat, $formattachments) { + global $DB; + $this->existence_check(); + + // Update the attributes. + $this->modified = $time; + $this->message = $postmessage; + $this->messageformat = $messageformat; + $this->formattachments = $formattachments; + + // Update the record in the database. + $DB->update_record('moodleoverflow_posts', $this->build_db_object()); + + // Update the attachments. This happens after the DB update call, as this function changes the DB record as well. + $this->moodleoverflow_add_attachment(); + + // Mark the edited post as read. + $this->mark_post_read(); + + // The post has been edited successfully. + return true; + } + + /** + * // NOTE: This function replaces the get_post_full() function but is not used until the print and print-related function for + * // printing the discussion and a post are adapted to the new post and discussion class. + * Gets a post with all info ready for moodleoverflow_print_post. + * Most of these joins are just to get the forum id. + * + * @return mixed array of posts or false + */ + public function moodleoverflow_get_complete_post() { + global $DB, $CFG; + $this->existence_check(); + + if ($CFG->branch >= 311) { + $allnames = \core_user\fields::for_name()->get_sql('u', false, '', '', false)->selects; + } else { + $allnames = implode(', ', fields::get_name_fields()); + } + $sql = "SELECT p.*, d.moodleoverflow, $allnames, u.email, u.picture, u.imagealt + FROM {moodleoverflow_posts} p + JOIN {moodleoverflow_discussions} d ON p.discussion = d.id + LEFT JOIN {user} u ON p.userid = u.id + WHERE p.id = " . $this->id . " ;"; + + $post = $DB->get_records_sql($sql); + if ($post->userid == 0) { + $post->message = get_string('privacy:anonym_post_message', 'mod_moodleoverflow'); + } + return $post; + } + + /** + * If successful, this function returns the name of the file + * + * @return bool + */ + public function moodleoverflow_add_attachment() { + global $DB; + $this->existence_check(); + + if (empty($this->formattachments)) { + return true; // Nothing to do. + } + + $context = \context_module::instance($this->get_coursemodule()->id); + $info = file_get_draft_area_info($this->formattachments); + $present = ($info['filecount'] > 0) ? '1' : ''; + file_save_draft_area_files($this->formattachments, $context->id, 'mod_moodleoverflow', 'attachment', $this->id, + \mod_moodleoverflow_post_form::attachment_options($this->get_moodleoverflow())); + $DB->set_field('moodleoverflow_posts', 'attachment', $present, ['id' => $this->id]); + } + + /** + * Returns attachments with information for the template + * + * + * @return array + */ + public function moodleoverflow_get_attachments() { + global $CFG, $OUTPUT; + $this->existence_check(); + + if (empty($this->attachment) || (!$context = \context_module::instance($this->get_coursemodule()->id))) { + return []; + } + + $attachments = []; + $fs = get_file_storage(); + + // We retrieve all files according to the time that they were created. In the case that several files were uploaded + // at the sametime (e.g. in the case of drag/drop upload) we revert to using the filename. + $files = $fs->get_area_files($context->id, 'mod_moodleoverflow', 'attachment', $this->id, "filename", false); + if ($files) { + $i = 0; + foreach ($files as $file) { + $attachments[$i] = []; + $attachments[$i]['filename'] = $file->get_filename(); + $mimetype = $file->get_mimetype(); + $iconimage = $OUTPUT->pix_icon(file_file_icon($file), + get_mimetype_description($file), 'moodle', + ['class' => 'icon']); + $path = moodle_url::make_pluginfile_url($file->get_contextid(), $file->get_component(), $file->get_filearea(), + $file->get_itemid(), $file->get_filepath(), $file->get_filename()); + $attachments[$i]['icon'] = $iconimage; + $attachments[$i]['filepath'] = $path; + + if (in_array($mimetype, ['image/gif', 'image/jpeg', 'image/png'])) { + // Image attachments don't get printed as links. + $attachments[$i]['image'] = true; + } else { + $attachments[$i]['image'] = false; + } + $i += 1; + } + } + return $attachments; + } + + // Getter. + + /** + * Getter for the postid + * @return int $this->id The post ID. + */ + public function get_id() { + $this->existence_check(); + return $this->id; + } + + /** + * Getter for the discussionid + * @return int $this->discussion The ID of the discussion where the post is located. + */ + public function get_discussionid() { + $this->existence_check(); + return $this->discussion; + } + + /** + * Getter for the parentid + * @return int $this->parent The ID of the parent post. + */ + public function get_parentid() { + $this->existence_check(); + return $this->parent; + } + + /** + * Getter for the userid + * @return int $this->userid The ID of the user who wrote the post. + */ + public function get_userid() { + $this->existence_check(); + return $this->userid; + } + + /** + * Returns the moodleoverflow where the post is located. + * @return object $moodleoverflowobject + */ + public function get_moodleoverflow() { + global $DB; + $this->existence_check(); + + if (empty($this->moodleoverflowobject)) { + $discussion = $this->get_discussion(); + $this->moodleoverflowobject = $DB->get_record('moodleoverflow', ['id' => $discussion->get_moodleoverflowid()]); + } + + return $this->moodleoverflowobject; + } + + /** + * Returns the discussion where the post is located. + * + * @return object $discussionobject. + */ + public function get_discussion() { + global $DB; + $this->existence_check(); + + if (empty($this->discussionobject)) { + $record = $DB->get_record('moodleoverflow_discussions', ['id' => $this->discussion]); + $this->discussionobject = discussion::from_record($record); + } + return $this->discussionobject; + } + + /** + * Returns the coursemodule + * + * @return object $cmobject + */ + public function get_coursemodule() { + $this->existence_check(); + + if (empty($this->cmobject)) { + $this->cmobject = \get_coursemodule_from_instance('moodleoverflow', $this->get_moodleoverflow()->id); + } + + return $this->cmobject; + } + + /** + * Returns the parent post + * @return object|false $post|false + */ + public function moodleoverflow_get_parentpost() { + global $DB; + $this->existence_check(); + + if ($this->parent == 0) { + // This post is the parent post. + $this->parentpost = false; + return false; + } + + if (empty($this->parentpost)) { + $parentpostrecord = $DB->get_record('moodleoverflow_post', ['id' => $this->parent]); + $this->parentpost = $this->from_record($parentpostrecord); + } + return $this->parentpost; + } + + /** + * Returns children posts (answers) as DB-records. + * + * @return array|false children/answer posts. + */ + public function moodleoverflow_get_childposts() { + global $DB; + $this->existence_check(); + + if ($childposts = $DB->get_records('moodleoverflow_posts', ['parent' => $this->id])) { + return $childposts; + } + + return false; + } + + /** + * This getter works as an help function in case another file/function needs the db-object of this instance (as the function + * is not adapted/refactored to the new way of working with discussion). + * @return object + */ + public function get_db_object() { + $this->existence_check(); + return $this->build_db_object(); + } + + // Helper Functions. + + /** + * Calculate the ratings of a post. + * + * @return object $ratingsobject. + */ + public function moodleoverflow_get_post_ratings() { + $this->existence_check(); + + $discussionid = $this->get_discussion()->id; + $postratings = \mod_moodleoverflow\ratings::moodleoverflow_get_ratings_by_discussion($discussionid, $this->id); + + $ratingsobject = new \stdClass(); + $ratingsobject->upvotes = $postratings->upvotes; + $ratingsobject->downvotes = $postratings->downvotes; + $ratingsobject->votesdifference = $postratings->upvotes - $postratings->downvotes; + $ratingsobject->markedhelpful = $postratings->ishelpful; + $ratingsobject->markedsolution = $postratings->issolved; + + return $ratingsobject; + } + + /** + * Marks the post as read if the user is tracking the discussion. + * Uses function from mod_moodleoverflow\readtracking. + */ + public function mark_post_read() { + global $USER; + $cantrack = readtracking::moodleoverflow_can_track_moodleoverflows($this->get_moodleoverflow()); + $istracked = readtracking::moodleoverflow_is_tracked($this->get_moodleoverflow()); + if ($cantrack && $istracked) { + // Please be aware that in future the use of get_db_object() should be replaced with only $this, + // as the readtracking class should be refactored with the new way of working with posts. + readtracking::moodleoverflow_mark_post_read($USER->id, $this->get_db_object()); + } + } + + /** + * Builds an object from this instance that has only DB-relevant attributes. + * @return object $dbobject + */ + private function build_db_object() { + $dbobject = new \stdClass(); + $dbobject->id = $this->id; + $dbobject->discussion = $this->discussion; + $dbobject->parent = $this->parent; + $dbobject->userid = $this->userid; + $dbobject->created = $this->created; + $dbobject->modified = $this->modified; + $dbobject->message = $this->message; + $dbobject->messageformat = $this->messageformat; + $dbobject->attachment = $this->attachment; + $dbobject->mailed = $this->mailed; + $dbobject->reviewed = $this->reviewed; + $dbobject->timereviewed = $this->timereviewed; + + return $dbobject; + } + + /** + * Count all replies of a post. + * + * @param bool $onlyreviewed Whether to count only reviewed posts. + * @return int Amount of replies + */ + public function moodleoverflow_count_replies($onlyreviewed) { + global $DB; + + $conditions = ['parent' => $this->id]; + + if ($onlyreviewed) { + $conditions['reviewed'] = '1'; + } + + // Return the amount of replies. + return $DB->count_records('moodleoverflow_posts', $conditions); + } + + // Security. + + /** + * Makes sure that the instance exists in the database. Every function in this class requires this check + * (except the function that adds a post to the database) + * + * @return true + * @throws moodle_exception + */ + private function existence_check() { + if (empty($this->id) || $this->id == false || $this->id == null) { + throw new moodle_exception('noexistingpost', 'moodleoverflow'); + } + return true; + } +} diff --git a/classes/post/post_control.php b/classes/post/post_control.php new file mode 100644 index 0000000000..2e692c3060 --- /dev/null +++ b/classes/post/post_control.php @@ -0,0 +1,889 @@ +. + +/** + * Class that is important to interact with posts. + * + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +namespace mod_moodleoverflow\post; + +// Import namespace from the locallib, needs a check later which namespaces are really needed. +use mod_moodleoverflow\anonymous; +use mod_moodleoverflow\capabilities; +use mod_moodleoverflow\event\discussion_created; +use mod_moodleoverflow\event\post_created; +use mod_moodleoverflow\event\post_updated; +use mod_moodleoverflow\review; + +use mod_moodleoverflow\post\post; +use mod_moodleoverflow\discussion\discussion; +use mod_moodleoverflow\subscriptions; +use moodle_exception; +use stdClass; + +defined('MOODLE_INTERNAL') || die(); +global $CFG; + +require_once($CFG->dirroot . '/mod/moodleoverflow/locallib.php'); +require_once($CFG->libdir . '/completionlib.php'); + +/** + * This Class controls the manipulation of posts and acts as controller of interactions with the post.php + * + * This Class has 2 main Tasks: + * 1. Before entering the post.php + * - Detect the wanted interaction (new discussion, new answer in a discussion, editing or deleting a post) + * - make capability and other security/integrity checks (are all given data correct?) + * - gather important information that need to be used later. + * Note: if a post is being deleted, the post_control deletes it in the first step and the post.php does not call the post_form.php + * + * Now the post.php calls the post_form, so that the user can enter a message and attachments. + * + * 2. After calling the post_form: + * - collect the information from the post_form + * - based on the interaction, call the right function + * + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class post_control { + + /** @var string the Interaction type, the interactions are: + * - create (creates a new discussion with a first post) + * - reply (replies to a existing post, can be an answer or a comment) + * - edit (change the content of an existing post) + * - delete (delete a post from a discussion) + */ + private $interaction; + + /** @var object information about the post like the related moodleoverflow, post etc. + * Difference between info and prepost: Info has objects, prepost mostly ID's and string like the message of the post. + */ + private $info; + + /** @var object prepost for the classes/post/post_form.php, + * this object is more like a prototype of a post and it's not in the database* + * Difference between info and prepost. Info has objects, prepost mostly ID's and strings like the message of the post. + */ + private $prepost; + + /** + * Constructor + */ + public function __construct() { + $this->info = new stdClass(); + } + + /** + * Detects the interaction and builds the prepost. + * @param stdClass $urlparameter parameter from the post.php + * @throws \coding_exception + * @throws moodle_exception if the interaction is not correct. + */ + public function detect_interaction($urlparameter) { + $count = 0; + $count += $urlparameter->create ? 1 : 0; + $count += $urlparameter->reply ? 1 : 0; + $count += $urlparameter->edit ? 1 : 0; + $count += $urlparameter->delete ? 1 : 0; + if ($count !== 1) { + throw new \coding_exception('Exactly one parameter should be specified!'); + } + + if ($urlparameter->create) { + $this->interaction = 'create'; + $this->info->moodleoverflowid = $urlparameter->create; + $this->build_prepost_create($this->info->moodleoverflowid); + + } else if ($urlparameter->edit) { + $this->interaction = 'edit'; + $this->info->editpostid = $urlparameter->edit; + $this->build_prepost_edit($this->info->editpostid); + + } else if ($urlparameter->reply) { + $this->interaction = 'reply'; + $this->info->replypostid = $urlparameter->reply; + $this->build_prepost_reply($this->info->replypostid); + + } else if ($urlparameter->delete) { + $this->interaction = 'delete'; + $this->info->deletepostid = $urlparameter->delete; + $this->build_prepost_delete($this->info->deletepostid); + } else { + throw new moodle_exception('unknownaction'); + } + } + + /** + * Controls the execution of an interaction. + * @param object $form The results from the post_form. + */ + public function execute_interaction($form) { + global $CFG; + // Redirect url in case of occurring errors. + if (empty($SESSION->fromurl)) { + $errordestination = $CFG->wwwroot . '/mod/moodleoverflow/view.php?m=' . $this->prepost->moodleoverflowid; + } else { + $errordestination = $SESSION->fromurl; + } + + // Format the submitted data. + $this->prepost->messageformat = $form->message['format']; + $this->prepost->formattachments = $form->attachments; + $this->prepost->message = $form->message['text']; + $this->prepost->messagetrust = trusttext_trusted($this->prepost->modulecontext); + + // Get the current time. + $this->prepost->timenow = time(); + + // Execute the right function. + if ($this->interaction == 'create' && $form->moodleoverflow == $this->prepost->moodleoverflowid) { + $this->execute_create($form, $errordestination); + } else if ($this->interaction == 'reply' && $form->reply == $this->prepost->parentid) { + $this->execute_reply($form, $errordestination); + } else if ($this->interaction == 'edit' && $form->edit == $this->prepost->postid) { + $this->execute_edit($form, $errordestination); + } else { + throw new moodle_exception('unexpectedinteractionerror', 'moodleoverflow', $errordestination); + } + } + + /** + * This function is used when a guest enters the post.php. + * Parameters will be checked so that the post.php can redirect the user to the right site. + * @param int $postid + * @param int $moodleoverflowid + * @return object $this->information // The gathered information. + */ + public function catch_guest($postid = false, $moodleoverflowid = false) { + global $PAGE; + if ((!$postid && !$moodleoverflowid) || ($postid && $moodleoverflowid)) { + throw new moodle_exception('inaccurateparameter', 'moodleoverflow'); + } + if ($postid) { + $this->collect_information($postid, false); + } else if ($moodleoverflowid) { + $this->collect_information(false, $moodleoverflowid); + } + $this->info->modulecontext = \context_module::instance($this->info->cm->id); + + // Set the parameters for the page. + $PAGE->set_cm($this->info->cm, $this->info->course, $this->info->moodleoverflow); + $PAGE->set_context($this->info->modulecontext); + $PAGE->set_title($this->info->course->shortname); + $PAGE->set_heading($this->info->course->fullname); + $PAGE->add_body_class('limitedwidth'); + return $this->info; + } + + // Build functions, that build the prepost object for further use. + + /** + * Function to prepare a new discussion in moodleoverflow. + * + * @param int $moodleoverflowid The ID of the moodleoverflow where the new discussion post is being created. + */ + private function build_prepost_create($moodleoverflowid) { + global $DB, $SESSION, $USER; + + // Get the related moodleoverflow, course coursemodule and the contexts. + $this->collect_information(false, $moodleoverflowid); + + // Check if the user can start a new discussion. + if (!$this->check_user_can_create_discussion()) { + + // Catch unenrolled user. + if (!isguestuser() && !is_enrolled($this->info->coursecontext)) { + if (enrol_selfenrol_available($this->info->course->id)) { + $SESSION->wantsurl = qualified_me(); + $SESSION->enrolcancel = get_local_referer(false); + redirect(new \moodle_url('/enrol/index.php', ['id' => $this->info->course->id, + 'returnurl' => '/mod/moodleoverflow/view.php?m=' . $this->info->moodleoverflow->id, ]), + get_string('youneedtoenrol')); + } + } + // Notify the user, that he can not post a new discussion. + throw new moodle_exception('nopostmoodleoverflow', 'moodleoverflow'); + } + + // Where is the user coming from? + $SESSION->fromurl = get_local_referer(false); + + // Prepare the post. + $this->assemble_prepost(); + $this->prepost->postid = null; + $this->prepost->discussionid = null; + $this->prepost->parentid = 0; + $this->prepost->subject = ''; + $this->prepost->userid = $USER->id; + $this->prepost->message = ''; + + // Unset where the user is coming from. + // Allows to calculate the correct return url later. + unset($SESSION->fromdiscussion); + } + + /** + * Function to prepare a new post that replies to an existing post. + * + * @param int $replypostid The ID of the post that is being answered. + */ + private function build_prepost_reply($replypostid) { + global $DB, $PAGE, $SESSION, $USER, $CFG; + + // Get the related poost, discussion, moodleoverflow, course, coursemodule and contexts. + $this->collect_information($replypostid, false); + + // Ensure the coursemodule is set correctly. + $PAGE->set_cm($this->info->cm, $this->info->course, $this->info->moodleoverflow); + + // Prepare a post. + $this->assemble_prepost(); + $this->prepost->postid = null; + $this->prepost->parentid = $this->info->relatedpost->get_id(); + $this->prepost->userid = $USER->id; + $this->prepost->message = ''; + + // Check whether the user is allowed to post. + if (!$this->check_user_can_create_reply()) { + + // Give the user the chance to enroll himself to the course. + if (!isguestuser() && !is_enrolled($this->info->coursecontext)) { + $SESSION->wantsurl = qualified_me(); + $SESSION->enrolcancel = get_local_referer(false); + redirect(new \moodle_url('/enrol/index.php', + ['id' => $this->info->course->id, + 'returnurl' => '/mod/moodleoverflow/view.php?m=' . $this->info->moodleoverflow->id, + ]), get_string('youneedtoenrol')); + } + // Print the error message. + throw new moodle_exception('nopostmoodleoverflow', 'moodleoverflow'); + } + // Make sure the user can post here. + if (!$this->info->cm->visible && !has_capability('moodle/course:viewhiddenactivities', $this->info->modulecontext)) { + throw new moodle_exception('activityiscurrentlyhidden'); + } + + // Append 'RE: ' to the discussions subject. + $strre = get_string('re', 'moodleoverflow'); + if (check_php_version('8.0.0')) { + if (!(str_starts_with($this->prepost->subject, $strre))) { + $this->prepost->subject = $strre . ' ' . $this->prepost->subject; + } + } else { + // LEARNWEB-TODO: remove this else branch when support for php version 7.4 ends. + if (!(substr($this->prepost->subject, 0, strlen($strre)) == $strre)) { + $this->prepost->subject = $strre . ' ' . $this->prepost->subject; + } + } + + // Unset where the user is coming from. + // Allows to calculate the correct return url later. + unset($SESSION->fromdiscussion); + } + + /** + * Function to prepare the edit of an user own existing post. + * + * @param int $editpostid The ID of the post that is being edited. + */ + private function build_prepost_edit($editpostid) { + global $DB, $PAGE, $SESSION, $USER; + + // Get the related post, discussion, moodleoverflow, course, coursemodule and contexts. + $this->collect_information($editpostid, false); + + // Set the pages context. + $PAGE->set_cm($this->info->cm, $this->info->course, $this->info->moodleoverflow); + + // Check if the post can be edited. + $beyondtime = ((time() - $this->info->relatedpost->created) > get_config('moodleoverflow', 'maxeditingtime')); + + // Please be aware that in future the use of get_db_object() should be replaced with $this->info->relatedpost, + // as the review class should be refactored with the new way of working with posts. + $alreadyreviewed = review::should_post_be_reviewed($this->info->relatedpost->get_db_object(), $this->info->moodleoverflow) + && $this->info->relatedpost->reviewed; + if (($beyondtime || $alreadyreviewed) && !has_capability('mod/moodleoverflow:editanypost', + $this->info->modulecontext)) { + throw new moodle_exception('maxtimehaspassed', 'moodleoverflow', '', + format_time(get_config('moodleoverflow', 'maxeditingtime'))); + } + + // If the current user is not the one who posted this post. + if ($this->info->relatedpost->get_userid() != $USER->id) { + + // Check if the current user has not the capability to edit any post. + if (!has_capability('mod/moodleoverflow:editanypost', $this->info->modulecontext)) { + + // Display the error. Capabilities are missing. + throw new moodle_exception('cannoteditposts', 'moodleoverflow'); + } + } + + // Load the $post variable. + $this->assemble_prepost(); + + // Unset where the user is coming from. This allows to calculate the correct return url later. + unset($SESSION->fromdiscussion); + } + + /** + * Function to prepare the deletion of a post. + * + * @param int $deletepostid The ID of the post that is being deleted. + */ + private function build_prepost_delete($deletepostid) { + global $DB, $USER; + + // Get the related post, discussion, moodleoverflow, course, coursemodule and contexts. + $this->collect_information($deletepostid, false); + + // Require a login and retrieve the modulecontext. + require_login($this->info->course, false, $this->info->cm); + + // Check some capabilities. + $this->check_user_can_delete_post(); + + // Count all replies of this post. + $this->info->replycount = $this->info->relatedpost->moodleoverflow_count_replies(false); + if ($this->info->replycount >= 1) { + $this->info->deletetype = 'plural'; + } else { + $this->info->deletetype = 'singular'; + } + // Build the prepost. + $this->assemble_prepost(); + $this->prepost->deletechildren = true; + } + + // Execute Functions. + + /** + * Executes the creation of a new discussion. + * + * @param object $form The results from the post_form. + * @param string $errordestination The URL to redirect to in case of an error. + * @throws moodle_exception if the discussion could not be added. + */ + private function execute_create($form, $errordestination) { + global $USER; + // Check if the user is allowed to post. + $this->check_user_can_create_discussion(); + + // Set the post to not reviewed if questions should be reviewed and the user is not a reviewed themselves. + if (review::get_review_level($this->info->moodleoverflow) >= review::QUESTIONS && + !capabilities::has(capabilities::REVIEW_POST, $this->info->modulecontext, $USER->id)) { + $this->prepost->reviewed = 0; + } else { + $this->prepost->reviewed = 1; + } + + // Get the discussion subject. + $this->prepost->subject = $form->subject; + + // Create the discussion object. + $discussion = discussion::construct_without_id($this->prepost->courseid, $this->prepost->moodleoverflowid, + $this->prepost->subject, 0, $this->prepost->userid, + $this->prepost->timenow, $this->prepost->timenow, $this->prepost->userid); + if (!$discussion->moodleoverflow_add_discussion($this->prepost)) { + throw new moodle_exception('couldnotadd', 'moodleoverflow', $errordestination); + } + + // The creation was successful. + $redirectmessage = \html_writer::tag('p', get_string("postaddedsuccess", "moodleoverflow")); + + // Trigger the discussion created event. + $params = ['context' => $this->info->modulecontext, 'objectid' => $discussion->get_id()]; + $event = discussion_created::create($params); + $event->trigger(); + + // Subscribe to this thread. + // Please be aware that in future the use of get_db_object() should be replaced with only $this->info->discussion, + // as the subscription class should be refactored with the new way of working with posts. + subscriptions::moodleoverflow_post_subscription($form, $this->info->moodleoverflow, + $discussion->get_db_object(), + $this->info->modulecontext); + + // Define the location to redirect the user after successfully posting. + $redirectto = new \moodle_url('/mod/moodleoverflow/view.php', ['m' => $form->moodleoverflow]); + redirect(moodleoverflow_go_back_to($redirectto->out()), $redirectmessage, null, \core\output\notification::NOTIFY_SUCCESS); + } + + /** + * Executes the reply to an existing post. + * + * @param object $form The results from the post_form. + * @param string $errordestination The URL to redirect to in case of an error. + * @throws moodle_exception if the reply could not be added. + */ + private function execute_reply($form, $errordestination) { + // Check if the user has the capability to write a reply. + $this->check_user_can_create_reply(); + + // Set to not reviewed, if posts should be reviewed, and user is not a reviewer themselves. + if (review::get_review_level($this->info->moodleoverflow) == review::EVERYTHING && + !has_capability('mod/moodleoverflow:reviewpost', \context_module::instance($this->info->cm->id))) { + $this->prepost->reviewed = 0; + } else { + $this->prepost->reviewed = 1; + } + + // Create the new post. + if (!$newpostid = $this->info->discussion->moodleoverflow_add_post_to_discussion($this->prepost)) { + throw new moodle_exception('couldnotadd', 'moodleoverflow', $errordestination); + } + + // The creation was successful. + $redirectmessage = \html_writer::tag('p', get_string("postaddedsuccess", "moodleoverflow")); + $redirectmessage .= \html_writer::tag('p', get_string("postaddedtimeleft", "moodleoverflow", + format_time(get_config('moodleoverflow', 'maxeditingtime')))); + + // Trigger the post created event. + $params = ['context' => $this->info->modulecontext, 'objectid' => $newpostid, + 'other' => ['discussionid' => $this->prepost->discussionid, + 'moodleoverflowid' => $this->prepost->moodleoverflowid, + ], + ]; + $event = post_created::create($params); + $event->trigger(); + + // Subscribe to this thread. + // Please be aware that in future the use of build_db_object() should be replaced with only $this->info->discussion, + // as the subscription class should be refactored with the new way of working with posts. + subscriptions::moodleoverflow_post_subscription($form, $this->info->moodleoverflow, + $this->info->discussion->get_db_object(), + $this->info->modulecontext); + + // Define the location to redirect the user after successfully posting. + $redirectto = new \moodle_url('/mod/moodleoverflow/discussion.php', + ['d' => $this->prepost->discussionid, 'p' => $newpostid]); + redirect(\moodleoverflow_go_back_to($redirectto->out()), $redirectmessage, null, \core\output\notification::NOTIFY_SUCCESS); + + } + + /** + * Executes the edit of an existing post. + * + * @param object $form The results from the post_form. + * @param string $errordestination The URL to redirect to in case of an error. + * @throws moodle_exception if the post could not be updated. + */ + private function execute_edit($form, $errordestination) { + global $USER, $DB; + // Check if the user has the capability to edit his post. + $this->check_user_can_edit_post(); + + // If the post that is being edited is the parent post, the subject can be edited too. + if ($this->prepost->parentid == 0) { + $this->prepost->subject = $form->subject; + } + + // Update the post. + if (!$this->info->discussion->moodleoverflow_edit_post_from_discussion($this->prepost)) { + throw new moodle_exception('couldnotupdate', 'moodleoverflow', $errordestination); + } + + // The edit was successful. + $redirectmessage = get_string('postupdated', 'moodleoverflow'); + if ($this->prepost->userid == $USER->id) { + $redirectmessage = get_string('postupdated', 'moodleoverflow'); + } else { + if (anonymous::is_post_anonymous($this->info->discussion, $this->info->moodleoverflow, $this->prepost->userid)) { + $name = get_string('anonymous', 'moodleoverflow'); + } else { + $realuser = $DB->get_record('user', ['id' => $this->prepost->userid]); + $name = fullname($realuser); + } + $redirectmessage = get_string('editedpostupdated', 'moodleoverflow', $name); + } + + // Trigger the post updated event. + $params = ['context' => $this->info->modulecontext, 'objectid' => $form->edit, + 'other' => ['discussionid' => $this->prepost->discussionid, + 'moodleoverflowid' => $this->prepost->moodleoverflowid, + ], + 'relateduserid' => $this->prepost->userid == $USER->id ? $this->prepost->userid : null, + ]; + $event = post_updated::create($params); + $event->trigger(); + + // Define the location to redirect the user after successfully editing. + $redirectto = new \moodle_url('/mod/moodleoverflow/discussion.php', + ['d' => $this->prepost->discussionid, 'p' => $form->edit]); + redirect(moodleoverflow_go_back_to($redirectto->out()), $redirectmessage, null, \core\output\notification::NOTIFY_SUCCESS); + } + + /** + * Executes the deletion of a post. + * + * @throws moodle_exception if the post could not be deleted. + */ + public function execute_delete() { + $this->check_interaction('delete'); + + // Check if the user has the capability to delete the post. + $timepassed = time() - $this->info->relatedpost->created; + $url = new \moodle_url('/mod/moodleoverflow/discussion.php', ['d' => $this->info->discussion->get_id()]); + if (($timepassed > get_config('moodleoverflow', 'maxeditingtime')) && !$this->info->deleteanypost) { + throw new moodle_exception('cannotdeletepost', 'moodleoverflow', moodleoverflow_go_back_to($url)); + } + + // A normal user cannot delete his post if there are direct replies. + if ($this->info->replycount && !$this->info->deleteanypost) { + throw new moodle_exception('cannotdeletereplies', 'moodleoverflow', moodleoverflow_go_back_to($url)); + } + + // Check if the post is a parent post or not. + if ($this->prepost->parentid == 0) { + // Save the moodleoverflowid. Then delete the discussion. + $moodleoverflowid = $this->info->discussion->get_moodleoverflowid(); + $this->info->discussion->moodleoverflow_delete_discussion($this->prepost); + + // Redirect the user back to the start page of the moodleoverflow instance. + redirect('view.php?m=' . $moodleoverflowid); + } else { + $this->info->discussion->moodleoverflow_delete_post_from_discussion($this->prepost); + $discussionurl = new \moodle_url('/mod/moodleoverflow/discussion.php', ['d' => $this->info->discussion->get_id()]); + redirect(moodleoverflow_go_back_to($discussionurl)); + } + } + + // Functions that uses the post.php to build the page. + + /** + * Builds a part of confirmation page. The confirmation request box is being build by the post.php. + */ + public function confirm_delete() { + $this->check_interaction('delete'); + global $PAGE; + moodleoverflow_set_return(); + $PAGE->navbar->add(get_string('delete', 'moodleoverflow')); + $PAGE->set_title($this->info->course->shortname); + $PAGE->set_heading($this->info->course->fullname); + $PAGE->add_body_class('limitedwidth'); + } + + /** + * + * Builds and returns a post_form object where the users enters/edits the message and attachments of the post. + * @param array $pageparams An object that the post.php created. + * @return object a mod_moodleoverflow_post_form object. + */ + public function build_postform($pageparams) { + global $USER, $CFG; + // Require that the user is logged in properly and enrolled to the course. + require_login($this->info->course, false, $this->info->cm); + + // Prepare the attachments. + $draftitemid = file_get_submitted_draft_itemid('attachments'); + file_prepare_draft_area($draftitemid, $this->info->modulecontext->id, 'mod_moodleoverflow', 'attachment', + empty($this->prepost->postid) ? null : $this->prepost->postid, + \mod_moodleoverflow_post_form::attachment_options($this->info->moodleoverflow)); + + // If the post is anonymous, attachments should have an anonymous author when editing the attachment. + if ($draftitemid && $this->interaction == 'edit' && anonymous::is_post_anonymous($this->info->discussion, + $this->info->moodleoverflow, $this->prepost->userid)) { + $usercontext = \context_user::instance($USER->id); + $anonymousstr = get_string('anonymous', 'moodleoverflow'); + foreach (get_file_storage()->get_area_files($usercontext->id, 'user', 'draft', $draftitemid) as $file) { + $file->set_author($anonymousstr); + } + } + + // Prepare the form. + $edit = $this->interaction == 'edit'; + $formarray = ['course' => $this->info->course, 'cm' => $this->info->cm, 'coursecontext' => $this->info->coursecontext, + 'modulecontext' => $this->info->modulecontext, 'moodleoverflow' => $this->info->moodleoverflow, + 'post' => $this->prepost, 'edit' => $edit, + ]; + + // Declare the post_form. + $mformpost = new \mod_moodleoverflow_post_form('post.php', $formarray, 'post', '', ['id' => 'mformmoodleoverflow']); + + // If the user is not the original author append an extra message to the message. (Happens when interaction = 'edit'). + if ($USER->id != $this->prepost->userid) { + // Create a temporary object. + $data = new stdClass(); + $data->date = userdate(time()); + $this->prepost->messageformat = editors_get_preferred_format(); + if ($this->prepost->messageformat == FORMAT_HTML) { + $data->name = \html_writer::tag('a', $CFG->wwwroot . '/user/view.php?id' . $USER->id . + '&course=' . $this->prepost->courseid . '">' . fullname($USER)); + $this->prepost->message .= \html_writer::tag('p', \html_writer::tag('span', + get_string('editedby', 'moodleoverflow', $data), ["class" => "edited"])); + } else { + $data->name = fullname($USER); + $this->prepost->message .= "\n\n(" . get_string('editedby', 'moodleoverflow', $data) . ')'; + } + // Delete the temporary object. + unset($data); + } + + // Define the heading for the form. + $formheading = ''; + if ($this->interaction == 'reply') { + $heading = get_string('yourreply', 'moodleoverflow'); + $formheading = get_string('reply', 'moodleoverflow'); + } else { + $heading = get_string('yournewtopic', 'moodleoverflow'); + } + + // Set data for the form. + $mformpost->set_data([ + 'attachments' => $draftitemid, + 'general' => $heading, + 'subject' => $this->prepost->subject, + 'message' => ['text' => $this->prepost->message, + 'format' => editors_get_preferred_format(), + 'itemid' => $this->prepost->postid, ], + 'userid' => $this->prepost->userid, + 'parent' => $this->prepost->parentid, + 'discussion' => $this->prepost->discussionid, + 'course' => $this->prepost->courseid, + ] + + $pageparams + ); + + return $mformpost; + } + + // Helper functions. + + // Getter. + + /** + * Returns the interaction type. + * @return string $interaction + */ + public function get_interaction() { + return $this->interaction; + } + + /** + * Returns the gathered important information in the build_prepost_() functions. + * @return object $info + */ + public function get_information() { + return $this->info; + } + + /** + * Retuns the prepared post. + * @return object $prepost + */ + public function get_prepost() { + return $this->prepost; + } + + // Functions that build the info and prepost object. + + /** + * Builds the information object that is being used in the build prepost functions. + * The variables are optional, but one is necessary to build the information object. + * @param int $postid + * @param int $moodleoverflowid + */ + private function collect_information($postid = false, $moodleoverflowid = false) { + if ($postid) { + // The related post is the post that is being answered, edited, or deleted. + $this->info->relatedpost = $this->check_post_exists($postid); + $this->info->discussion = $this->check_discussion_exists($this->info->relatedpost->get_discussionid()); + $localmoodleoverflowid = $this->info->discussion->get_moodleoverflowid(); + } else { + $localmoodleoverflowid = $moodleoverflowid; + } + $this->info->moodleoverflow = $this->check_moodleoverflow_exists($localmoodleoverflowid); + $this->info->course = $this->check_course_exists($this->info->moodleoverflow->course); + $this->info->cm = $this->check_coursemodule_exists($this->info->moodleoverflow->id, $this->info->course->id); + $this->info->modulecontext = \context_module::instance($this->info->cm->id); + $this->info->coursecontext = \context_course::instance($this->info->course->id); + } + + /** + * Assembles the prepost object. Helps to reduce code in the build_prepost functions. + * Some prepost parameters will be assigned individually by the build_prepost functions. + */ + private function assemble_prepost() { + $this->prepost = new stdClass(); + $this->prepost->courseid = $this->info->course->id; + $this->prepost->moodleoverflowid = $this->info->moodleoverflow->id; + $this->prepost->modulecontext = $this->info->modulecontext; + + if ($this->interaction != 'create') { + $this->prepost->discussionid = $this->info->discussion->get_id(); + $this->prepost->subject = $this->info->discussion->name; + + if ($this->interaction != 'reply') { + $this->prepost->parentid = $this->info->relatedpost->get_parentid(); + $this->prepost->postid = $this->info->relatedpost->get_id(); + $this->prepost->userid = $this->info->relatedpost->get_userid(); + $this->prepost->message = $this->info->relatedpost->message; + } + } + } + + + // Interaction check. + + /** + * Checks if the interaction is correct + * @param string $interaction + * @return true if the interaction is correct + */ + private function check_interaction($interaction) { + if ($this->interaction != $interaction) { + throw new moodle_exception('wronginteraction' , 'moodleoverflow'); + } + return true; + } + + // Database checks. + + /** + * Checks if the course exists. Returns the $DB->record of the course. + * @param int $courseid + * @return object $course + */ + private function check_course_exists($courseid) { + global $DB; + if (!$course = $DB->get_record('course', ['id' => $courseid])) { + throw new moodle_exception('invalidcourseid'); + } + return $course; + } + + /** + * Checks if the coursemodule exists. + * @param int $moodleoverflowid + * @param int $courseid + * @return object $cm + */ + private function check_coursemodule_exists($moodleoverflowid, $courseid) { + if (!$cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflowid, + $courseid)) { + throw new moodle_exception('invalidcoursemodule'); + } + return $cm; + } + + /** + * Checks if the related moodleoverflow exists. + * @param int $moodleoverflowid + * @return object $moodleoverflow + */ + private function check_moodleoverflow_exists($moodleoverflowid) { + // Get the related moodleoverflow instance. + global $DB; + if (!$moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $moodleoverflowid])) { + throw new moodle_exception('invalidmoodleoverflowid', 'moodleoverflow'); + } + return $moodleoverflow; + } + + /** + * Checks if the related discussion exists. + * @param int $discussionid + * @return object $discussion + */ + private function check_discussion_exists($discussionid) { + global $DB; + if (!$discussionrecord = $DB->get_record('moodleoverflow_discussions', ['id' => $discussionid])) { + throw new moodle_exception('invaliddiscussionid', 'moodleoverflow'); + } + return discussion::from_record($discussionrecord); + } + + /** + * Checks if a post exists. + * @param int $postid + * @return object $post + */ + private function check_post_exists($postid) { + global $DB; + if (!$postrecord = $DB->get_record('moodleoverflow_posts', ['id' => $postid])) { + throw new moodle_exception('invalidpostid', 'moodleoverflow'); + } + return post::from_record($postrecord); + } + + // Capability checks. + + /** + * Checks if a user can create a discussion. + * @return true + * @throws moodle_exception + */ + private function check_user_can_create_discussion() { + if (!has_capability('mod/moodleoverflow:startdiscussion', $this->info->modulecontext)) { + throw new moodle_exception('cannotcreatediscussion', 'moodleoverflow'); + } + return true; + } + + /** + * Checks if a user can reply in a discussion. + * @return true + * @throws moodle_exception + */ + private function check_user_can_create_reply() { + if (!has_capability('mod/moodleoverflow:replypost', $this->info->modulecontext, $this->prepost->userid)) { + throw new moodle_exception('cannotreply', 'moodleoverflow'); + } + return true; + } + + /** + * Checks if a user can edit a post. + * A user can edit if he can edit any post of if he edits his own post and has the ability to: + * start a new discussion or to reply to a post. + * + * @return true + * @throws moodle_exception + */ + private function check_user_can_edit_post() { + global $USER; + $editanypost = has_capability('mod/moodleoverflow:editanypost', $this->info->modulecontext); + $replypost = has_capability('mod/moodleoverflow:replypost', $this->info->modulecontext); + $startdiscussion = has_capability('mod/moodleoverflow:startdiscussion', $this->info->modulecontext); + $ownpost = ($this->prepost->userid == $USER->id); + if (!(($ownpost && ($replypost || $startdiscussion)) || $editanypost)) { + throw new moodle_exception('cannotupdatepost', 'moodleoverflow'); + } + return true; + } + + /** + * Checks if a user can edit a post. + * @return true + * @throws moodle_exception + */ + private function check_user_can_delete_post() { + global $USER; + $this->info->deleteownpost = has_capability('mod/moodleoverflow:deleteownpost', $this->info->modulecontext); + $this->info->deleteanypost = has_capability('mod/moodleoverflow:deleteanypost', $this->info->modulecontext); + if (!(($this->info->relatedpost->get_userid() == $USER->id && $this->info->deleteownpost) || $this->info->deleteanypost)) { + + throw new moodle_exception('cannotdeletepost', 'moodleoverflow'); + } + return true; + } +} diff --git a/classes/post_form.php b/classes/post_form.php index 06cd80f017..2e1c66e2b5 100644 --- a/classes/post_form.php +++ b/classes/post_form.php @@ -43,8 +43,9 @@ class mod_moodleoverflow_post_form extends moodleform { */ public function definition() { - $modform =& $this->_form; + $modform =& $this->_form; $post = $this->_customdata['post']; + $edit = $this->_customdata['edit']; $modcontext = $this->_customdata['modulecontext']; $moodleoverflow = $this->_customdata['moodleoverflow']; @@ -71,7 +72,7 @@ public function definition() { } // Submit buttons. - if (isset($post->edit)) { + if ($edit) { $strsubmit = get_string('savechanges'); } else { $strsubmit = get_string('posttomoodleoverflow', 'moodleoverflow'); @@ -162,10 +163,3 @@ public static function editor_options(context_module $context, $postid) { ]; } } - - - - - - - diff --git a/classes/review.php b/classes/review.php index a0a86a7d4d..a566e9896d 100644 --- a/classes/review.php +++ b/classes/review.php @@ -134,7 +134,7 @@ public static function get_first_review_post($moodleoverflowid, $afterpostid = n */ public static function should_post_be_reviewed($post, $moodleoverflow): bool { $reviewlevel = self::get_review_level($moodleoverflow); - if ($post->parent) { + if ($post->parent != 0) { return $reviewlevel == self::EVERYTHING; } else { return $reviewlevel >= self::QUESTIONS; diff --git a/classes/subscriptions.php b/classes/subscriptions.php index 2063c067f1..14fe64ceb7 100644 --- a/classes/subscriptions.php +++ b/classes/subscriptions.php @@ -26,6 +26,9 @@ namespace mod_moodleoverflow; +use context_module; +use stdClass; + /** * Moodleoverflow subscription manager. * @@ -356,7 +359,7 @@ public static function subscription_disabled($moodleoverflow) { * Checks wheter the specified moodleoverflow can be subscribed to. * * @param object $moodleoverflow The moodleoverflow ID - * @param \context_module $context The module context. + * @param context_module $context The module context. * * @return boolean */ @@ -456,7 +459,7 @@ public static function get_unsubscribable_moodleoverflows() { /** * Get the list of potential subscribers to a moodleoverflow. * - * @param \context_module $context The moodleoverflow context. + * @param context_module $context The moodleoverflow context. * @param string $fields The list of fields to return for each user. * @param string $sort Sort order. * @@ -522,7 +525,7 @@ public static function fill_subscription_cache_for_course($courseid, $userid) { * Returns a list of user object who are subscribed to this moodleoverflow. * * @param stdClass $moodleoverflow The moodleoverflow record - * @param \context_module $context The moodleoverflow context + * @param context_module $context The moodleoverflow context * @param string $fields Requested user fields * @param boolean $includediscussions Whether to take discussion subscriptions into consideration * @@ -641,8 +644,8 @@ public static function reset_moodleoverflow_cache() { * Adds user to the subscriber list. * * @param int $userid The user ID - * @param \stdClass $moodleoverflow The moodleoverflow record - * @param \context_module $context The module context + * @param stdClass $moodleoverflow The moodleoverflow record + * @param context_module $context The module context * @param bool $userrequest Whether the user requested this change themselves. * * @return bool|int Returns true if the user is already subscribed or the subscription id if successfully subscribed. @@ -656,7 +659,7 @@ public static function subscribe_user($userid, $moodleoverflow, $context, $userr } // Create a new subscription object. - $sub = new \stdClass(); + $sub = new stdClass(); $sub->userid = $userid; $sub->moodleoverflow = $moodleoverflow->id; @@ -706,8 +709,8 @@ public static function subscribe_user($userid, $moodleoverflow, $context, $userr * Removes user from the subscriber list. * * @param int $userid The user ID. - * @param \stdClass $moodleoverflow The moodleoverflow record - * @param \context_module $context The module context + * @param stdClass $moodleoverflow The moodleoverflow record + * @param context_module $context The module context * @param boolean $userrequest Whether the user requested this change themselves. * * @return bool Always returns true @@ -761,9 +764,10 @@ public static function unsubscribe_user($userid, $moodleoverflow, $context, $use /** * Subscribes the user to the specified discussion. * + * LEARNWEB-TODO: Refactor this function to the new way of working with discussion and posts. * @param int $userid The user ID - * @param \stdClass $discussion The discussion record - * @param \context_module $context The module context + * @param stdClass $discussion The discussion record + * @param context_module $context The module context * * @return bool Whether a change was made */ @@ -807,7 +811,7 @@ public static function subscribe_user_to_discussion($userid, $discussion, $conte } else { // Else a new record needs to be created. - $subscription = new \stdClass(); + $subscription = new stdClass(); $subscription->userid = $userid; $subscription->moodleoverflow = $discussion->moodleoverflow; $subscription->discussion = $discussion->id; @@ -836,9 +840,9 @@ public static function subscribe_user_to_discussion($userid, $discussion, $conte /** * Unsubscribes the user from the specified discussion. * - * @param int $userid The user ID - * @param \stdClass $discussion The discussion record - * @param \context_module $context The context module + * @param int $userid The user ID + * @param stdClass $discussion The discussion record + * @param context_module $context The context module * * @return bool Whether a change was made */ @@ -886,7 +890,7 @@ public static function unsubscribe_user_from_discussion($userid, $discussion, $c // There is no record. // Create a new discussion subscription record. - $subscription = new \stdClass(); + $subscription = new stdClass(); $subscription->userid = $userid; $subscription->moodleoverflow = $discussion->moodleoverflow; $subscription->discussion = $discussion->id; @@ -985,13 +989,15 @@ public static function moodleoverflow_get_subscribe_link($moodleoverflow, $conte /** * Given a new post, subscribes the user to the thread the post was posted in. * - * @param \stdClass $moodleoverflow The moodleoverflow record - * @param \stdClass $discussion The discussion record - * @param \context_module $modulecontext The context of the module + * LEARNWEB-TODO: Refactor this function to the new way of working with discussion and posts. + * @param object $fromform The submitted form + * @param stdClass $moodleoverflow The moodleoverflow record + * @param stdClass $discussion The discussion record + * @param context_module $modulecontext The context of the module * * @return bool */ - public static function moodleoverflow_post_subscription($moodleoverflow, $discussion, $modulecontext) { + public static function moodleoverflow_post_subscription($fromform, $moodleoverflow, $discussion, $modulecontext) { global $USER; // Check for some basic information. diff --git a/classes/tables/userstats_table.php b/classes/tables/userstats_table.php index ad663e6bc9..dc2db1e610 100644 --- a/classes/tables/userstats_table.php +++ b/classes/tables/userstats_table.php @@ -30,6 +30,7 @@ require_once($CFG->dirroot . '/mod/moodleoverflow/lib.php'); require_once($CFG->dirroot . '/mod/moodleoverflow/locallib.php'); require_once($CFG->libdir . '/tablelib.php'); +use mod_moodleoverflow\output\helpicon; /** * Table listing all user statistics of a course @@ -151,27 +152,11 @@ public function get_usertable() { * Setup the help icon for amount of activity */ public function set_helpactivity() { - global $CFG; + $htmlclass = 'helpactivityclass btn btn-link'; + $content = get_string('helpamountofactivity', 'moodleoverflow'); + $helpobject = new helpicon($htmlclass, $content); $this->helpactivity = new \stdClass(); - $this->helpactivity->iconurl = $CFG->wwwroot . '/pix/a/help.png'; - $this->helpactivity->icon = \html_writer::img($this->helpactivity->iconurl, - get_string('helpamountofactivity', 'moodleoverflow')); - $this->helpactivity->class = 'helpactivityclass btn btn-link'; - $this->helpactivity->iconattributes = ['role' => 'button', - 'data-container' => 'body', - 'data-toggle' => 'popover', - 'data-placement' => 'right', - 'data-action' => 'showhelpicon', - 'data-html' => 'true', - 'data-trigger' => 'focus', - 'tabindex' => '0', - 'data-content' => '

' . - get_string('helpamountofactivity', 'moodleoverflow') . - '

', ]; - - $this->helpactivity->object = \html_writer::span($this->helpactivity->icon, - $this->helpactivity->class, - $this->helpactivity->iconattributes); + $this->helpactivity->object = $helpobject->get_helpicon(); } // Functions that show the data. diff --git a/classes/task/send_mails.php b/classes/task/send_mails.php index 7991f55023..d39286e0b6 100644 --- a/classes/task/send_mails.php +++ b/classes/task/send_mails.php @@ -25,6 +25,7 @@ namespace mod_moodleoverflow\task; use core\session\exception; +use mod_moodleoverflow\anonymous; use mod_moodleoverflow\output\moodleoverflow_email; defined('MOODLE_INTERNAL') || die(); @@ -122,6 +123,7 @@ public function send_review_notifications() { $post = $postinfo; $userfrom = \core_user::get_user($postinfo->userid, '*', MUST_EXIST); + $userfrom->anonymous = anonymous::is_post_anonymous($discussion, $moodleoverflow, $postinfo->userid); foreach ($usersto as $userto) { try { diff --git a/db/access.php b/db/access.php index cfa4c6e2fe..1ea6074738 100644 --- a/db/access.php +++ b/db/access.php @@ -22,7 +22,7 @@ * the module version number should be bumped up. * * The system has four possible values for a capability: - * CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT, and inherit (not set). + * CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT, and inherit (not set]. * * It is important that capability names are unique. The naming convention * for capabilities that are specific to modules and blocks is as follows: diff --git a/db/install.xml b/db/install.xml index f7f28d27fb..9d3f323f5f 100644 --- a/db/install.xml +++ b/db/install.xml @@ -28,6 +28,8 @@ + + diff --git a/db/messages.php b/db/messages.php index 24a0c7f6a0..ee2af9a367 100644 --- a/db/messages.php +++ b/db/messages.php @@ -25,7 +25,6 @@ defined('MOODLE_INTERNAL') || die(); $messageproviders = [ - // Ordinary single moodleoverflow posts. 'posts' => [], ]; diff --git a/db/upgrade.php b/db/upgrade.php index dc813f23a9..ae8a539965 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -264,8 +264,7 @@ function xmldb_moodleoverflow_upgrade($oldversion) { $table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']); $table->add_key('courseid', XMLDB_KEY_FOREIGN, ['courseid'], 'course', ['id']); $table->add_key('forumid', XMLDB_KEY_FOREIGN, ['forumid'], 'moodleoverflow', ['id']); - $table->add_key('forumdiscussionid', XMLDB_KEY_FOREIGN, - ['forumdiscussionid'], 'moodleoverflow_discussions', ['id']); + $table->add_key('forumdiscussionid', XMLDB_KEY_FOREIGN, ['forumdiscussionid'], 'moodleoverflow_discussions', ['id']); // Conditionally launch create table for moodleoverflow_mail_info. if (!$dbman->table_exists($table)) { @@ -300,5 +299,22 @@ function xmldb_moodleoverflow_upgrade($oldversion) { upgrade_mod_savepoint(true, 2024072600, 'moodleoverflow'); } + if ($oldversion < 2025031200) { + // Define table moodleoverflow to be edited. + $table = new xmldb_table('moodleoverflow'); + + // Create the field fot the start time for the limited answer mode. + $field = new xmldb_field('la_starttime', XMLDB_TYPE_INTEGER, '10', null, null, null, 0, 'allowmultiplemarks'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + // Create the field for the end time for the limited answer mode. + $field = new xmldb_field('la_endtime', XMLDB_TYPE_INTEGER, '10', null, null, null, 0, 'la_starttime'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + upgrade_mod_savepoint(true, 2025031200, 'moodleoverflow'); + } + return true; } diff --git a/discussion.php b/discussion.php index bb0cfec030..488e34e14a 100644 --- a/discussion.php +++ b/discussion.php @@ -55,6 +55,8 @@ if ($marksetting->allowmultiplemarks == 1) { $multiplemarks = true; } +// Setting of limitedanswer. Limitedanswertime saves the timestamp, until the limitedanswer is on (0 if off). +$limitedanswersetting = $DB->get_record('moodleoverflow', ['id' => $moodleoverflow->id], 'la_starttime, la_endtime'); // Get the related coursemodule and its context. if (!$cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id, $course->id)) { @@ -156,7 +158,7 @@ echo '
'; -moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discussion, $post, $multiplemarks); +moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discussion, $post, $multiplemarks, $limitedanswersetting); echo '
'; echo $OUTPUT->footer(); diff --git a/externallib.php b/externallib.php index 8e148d368f..98d8739e3b 100644 --- a/externallib.php +++ b/externallib.php @@ -22,6 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use mod_moodleoverflow\anonymous; use mod_moodleoverflow\output\moodleoverflow_email; use mod_moodleoverflow\review; @@ -39,6 +40,7 @@ */ class mod_moodleoverflow_external extends external_api { + // LEARNWEB-TODO: Adapt the functions to the new way of working with posts. /** * Returns description of method parameters * @return external_function_parameters @@ -54,7 +56,7 @@ public static function record_vote_parameters() { /** * Returns the result of the vote (new rating and reputations). - * @return external_multiple_structure + * @return external_single_structure */ public static function record_vote_returns() { return new external_single_structure( @@ -121,7 +123,7 @@ public static function record_vote($postid, $ratingid) { $ownerrating = \mod_moodleoverflow\ratings::moodleoverflow_get_reputation($moodleoverflow->id, $postownerid); $raterrating = \mod_moodleoverflow\ratings::moodleoverflow_get_reputation($moodleoverflow->id, $USER->id); - $cannotseeowner = \mod_moodleoverflow\anonymous::is_post_anonymous($discussion, $moodleoverflow, $USER->id) && + $cannotseeowner = anonymous::is_post_anonymous($discussion, $moodleoverflow, $USER->id) && $USER->id != $postownerid; $params['postrating'] = $rating->upvotes - $rating->downvotes; @@ -254,6 +256,7 @@ public static function review_reject_post($postid, $reason = null) { $renderertext = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'textemail'); $userto = core_user::get_user($post->userid); + $userto->anonymous = anonymous::is_post_anonymous($discussion, $moodleoverflow, $post->userid); $maildata = new moodleoverflow_email( $course, diff --git a/index.php b/index.php index cac34f312a..193d769764 100644 --- a/index.php +++ b/index.php @@ -25,6 +25,8 @@ require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); global $CFG, $DB, $PAGE, $USER, $SESSION, $OUTPUT; require_once(dirname(__FILE__) . '/locallib.php'); + +global $CFG, $USER, $DB, $PAGE, $SESSION, $OUTPUT; require_once($CFG->dirroot . '/course/lib.php'); // Require needed files. @@ -56,9 +58,7 @@ unset($SESSION->fromdiscussion); // Trigger the course module instace lise viewed evewnt. -$params = [ - 'context' => context_course::instance($course->id), -]; +$params = ['context' => context_course::instance($course->id)]; $event = \mod_moodleoverflow\event\course_module_instance_list_viewed::create($params); $event->add_record_snapshot('course', $course); $event->trigger(); diff --git a/lang/en/moodleoverflow.php b/lang/en/moodleoverflow.php index f2c65c5235..169b594b81 100644 --- a/lang/en/moodleoverflow.php +++ b/lang/en/moodleoverflow.php @@ -172,7 +172,21 @@ $string['invalidpostid'] = 'Invalid post ID - {$a}'; $string['invalidratingid'] = 'The submitted rating is neither an upvote nor a downvote.'; $string['jump_to_next_post_needing_review'] = 'Jump to next post needing to be reviewed.'; +$string['la_endtime'] = 'Time at which students can no longer answer'; +$string['la_endtime_help'] = 'Students can not answer to qustions after the set up date'; +$string['la_endtime_ruleerror'] = 'End time must be in the future'; +$string['la_sequence_error'] = 'The end time must be after the start time'; +$string['la_starttime'] = 'Time at which students can start to answer'; +$string['la_starttime_help'] = 'Students can not answer to questions until the set up date'; +$string['la_starttime_ruleerror'] = 'Start time must be in the future'; $string['lastpost'] = 'Last post'; +$string['limitedanswer_helpicon_teacher'] = 'This can be changed in the settings of the Moodleoverflow.'; +$string['limitedanswer_info_endtime'] = 'Posts can not be answered after {$a->limitedanswerdate}.'; +$string['limitedanswer_info_start'] = 'This Moodleoverflow is in a limited answer mode.'; +$string['limitedanswer_info_starttime'] = 'Posts can not be answered until {$a->limitedanswerdate}.'; +$string['limitedanswerheading'] = 'Limited Answer Mode'; +$string['limitedanswerwarning_answers'] = 'There are already answered posts in this Moodleoverflow.'; +$string['limitedanswerwarning_conclusion'] = 'You can only set a time until students are able to answer'; $string['mailindexlink'] = 'Change your forum preferences: {$a}'; $string['manydiscussions'] = 'Discussions per page'; $string['markallread'] = 'Mark all posts in this discussion as read'; @@ -257,9 +271,7 @@ $string['postaddedtimeleft'] = 'You have {$a} to edit it if you want to make any changes.'; $string['postbyuser'] = '{$a->post} by {$a->user}'; $string['postincontext'] = 'See this post in context'; -$string['postmailinfolink'] = 'This is a copy of a message posted in {$a->coursename}. - -To reply click on this link: {$a->replylink}'; +$string['postmailinfolink'] = 'This is a copy of a message posted in {$a->coursename}. To reply click on this link: {$a->replylink}'; $string['postmailsubject'] = '{$a->courseshortname}: {$a->subject}'; $string['postnotexist'] = 'Requested post does not exist'; $string['posts'] = 'Posts'; diff --git a/lib.php b/lib.php index eb80c2df4a..d8c512e6be 100644 --- a/lib.php +++ b/lib.php @@ -29,6 +29,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +// LEARNWEB-TODO: Adapt functions to the new way of working with posts and discussions (Replace the post/discussion functions). use core\context\course; defined('MOODLE_INTERNAL') || die(); @@ -343,7 +344,7 @@ function moodleoverflow_print_recent_activity($course, $viewfullnames, $timestar /** * Returns all other caps used in the module. * - * For example, this could be array('moodle/site:accessallgroups') if the + * For example, this could be ['moodle/site:accessallgroups'] if the * module uses that capability. * * @return array @@ -820,10 +821,12 @@ function moodleoverflow_send_mails() { if (\mod_moodleoverflow\anonymous::is_post_anonymous($discussion, $moodleoverflow, $post->userid)) { $userfrom = \core_user::get_noreply_user(); + $userfrom->anonymous = true; } else { // Check whether the sending user is cached already. if (array_key_exists($post->userid, $users)) { $userfrom = $users[$post->userid]; + $userfrom->anonymous = false; } else { // We dont know the the user yet. @@ -831,6 +834,7 @@ function moodleoverflow_send_mails() { $userfrom = $DB->get_record('user', ['id' => $post->userid]); if ($userfrom) { moodleoverflow_minimise_user_record($userfrom); + $userfrom->anonymous = false; } else { $uid = $post->userid; $pid = $post->id; @@ -1093,7 +1097,7 @@ function moodleoverflow_mark_old_posts_as_mailed($endtime) { * * @param stdClass $user */ -function moodleoverflow_minimise_user_record(stdClass $user) { +function moodleoverflow_minimise_user_record(stdClass &$user) { // Remove all information for the mail generation that are not needed. unset($user->institution); diff --git a/locallib.php b/locallib.php index 375da72dfd..44b3a381d5 100644 --- a/locallib.php +++ b/locallib.php @@ -27,6 +27,8 @@ use mod_moodleoverflow\anonymous; use mod_moodleoverflow\capabilities; use mod_moodleoverflow\event\post_deleted; +use mod_moodleoverflow\output\helpicon; +use mod_moodleoverflow\ratings; use mod_moodleoverflow\readtracking; use mod_moodleoverflow\review; @@ -47,6 +49,8 @@ function moodleoverflow_get_discussions($cm, $page = -1, $perpage = 0) { global $DB, $CFG, $USER; + // LEARNWEB-TODO Refactor variable naming. $discussion->id is first post and $discussion->discussion is discussion id? + // User must have the permission to view the discussions. $modcontext = context_module::instance($cm->id); if (!capabilities::has(capabilities::VIEW_DISCUSSION, $modcontext)) { @@ -255,7 +259,7 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - } // Check if the question owner marked the question as helpful. - $markedhelpful = \mod_moodleoverflow\ratings::moodleoverflow_discussion_is_solved($discussion->discussion, false); + $markedhelpful = ratings::moodleoverflow_discussion_is_solved($discussion->discussion, false); $preparedarray[$i]['starterlink'] = null; if ($markedhelpful) { $link = '/mod/moodleoverflow/discussion.php?d='; @@ -266,7 +270,7 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - } // Check if a teacher marked a post as solved. - $markedsolution = \mod_moodleoverflow\ratings::moodleoverflow_discussion_is_solved($discussion->discussion, true); + $markedsolution = ratings::moodleoverflow_discussion_is_solved($discussion->discussion, true); $preparedarray[$i]['teacherlink'] = null; if ($markedsolution) { $link = '/mod/moodleoverflow/discussion.php?d='; @@ -285,7 +289,7 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - } // Get the amount of votes for the discussion. - $votes = \mod_moodleoverflow\ratings::moodleoverflow_get_ratings_by_discussion($discussion->discussion, $discussion->id); + $votes = ratings::moodleoverflow_get_ratings_by_discussion($discussion->discussion, $discussion->id); $votes = $votes->upvotes - $votes->downvotes; $preparedarray[$i]['votetext'] = ($votes == 1) ? 'vote' : 'votes'; @@ -397,13 +401,13 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - $preparedarray[$i]['votes'] = $votes; // Did the user rated this post? - $rating = \mod_moodleoverflow\ratings::moodleoverflow_user_rated($discussion->firstpost); + $rating = ratings::moodleoverflow_user_rated($discussion->firstpost); $firstpost = moodleoverflow_get_post_full($discussion->firstpost); $preparedarray[$i]['userupvoted'] = ($rating->rating ?? null) == RATING_UPVOTE; $preparedarray[$i]['userdownvoted'] = ($rating->rating ?? null) == RATING_DOWNVOTE; - $preparedarray[$i]['canchange'] = \mod_moodleoverflow\ratings::moodleoverflow_user_can_rate($firstpost, $context) && + $preparedarray[$i]['canchange'] = ratings::moodleoverflow_user_can_rate($firstpost, $context) && $startuser->id != $USER->id; $preparedarray[$i]['postid'] = $discussion->firstpost; @@ -527,6 +531,7 @@ function moodleoverflow_count_discussion_replies($cm) { } /** + * LEARNWEB-TODO: Delete this function when adapting the print-functions to the new post and discussion structure. * Check if the user is capable of starting a new discussion. * * @param object $moodleoverflow @@ -537,7 +542,7 @@ function moodleoverflow_count_discussion_replies($cm) { */ function moodleoverflow_user_can_post_discussion($moodleoverflow, $cm = null, $context = null) { - // Guests an not-logged-in users can not psot. + // Guests an not-logged-in users can not post. if (isguestuser() || !isloggedin()) { return false; } @@ -653,13 +658,8 @@ function moodleoverflow_get_discussions_unread($cm) { * @return mixed array of posts or false */ function moodleoverflow_get_post_full($postid) { - global $DB, $CFG; - - if ($CFG->branch >= 311) { - $allnames = \core_user\fields::for_name()->get_sql('u', false, '', '', false)->selects; - } else { - $allnames = get_all_user_name_fields(true, 'u'); - } + global $DB; + $allnames = \core_user\fields::for_name()->get_sql('u', false, '', '', false)->selects; $sql = "SELECT p.*, d.moodleoverflow, $allnames, u.email, u.picture, u.imagealt FROM {moodleoverflow_posts} p JOIN {moodleoverflow_discussions} d ON p.discussion = d.id @@ -925,16 +925,19 @@ function moodleoverflow_user_can_post($modulecontext, $posttoreplyto, $considerr /** * Prints a moodleoverflow discussion. + * LEARNWEB-TODO: REFACTOR WITH NEW POST AND DISCUSSION STRUCTURE. * - * @param stdClass $course The course object + * @param stdClass $course The course object * @param object $cm - * @param stdClass $moodleoverflow The moodleoverflow object - * @param stdClass $discussion The discussion object - * @param stdClass $post The post object - * @param bool $multiplemarks The setting of multiplemarks (default: multiplemarks are not allowed) + * @param stdClass $moodleoverflow The moodleoverflow object + * @param stdClass $discussion The discussion object + * @param stdClass $post The post object + * @param bool $multiplemarks The setting of multiplemarks (default: multiplemarks are not allowed) + * @param stdClass|null $limitedanswersetting Two Unix timestamp wrapped in a stdClass, upper and lower label for answering. */ -function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discussion, $post, $multiplemarks = false) { - global $USER; +function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discussion, $post, + $multiplemarks = false, ?stdClass $limitedanswersetting = null) { + global $USER, $DB; // Check if the current is the starter of the discussion. $ownpost = (isloggedin() && ($USER->id == $post->userid)); @@ -945,6 +948,7 @@ function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discuss $istracked = readtracking::moodleoverflow_is_tracked($moodleoverflow); // Retrieve all posts of the discussion. + // This part is adapted/refactored to the new way of working with posts (use of get_id() function and discussion object). $posts = moodleoverflow_get_all_discussion_posts($discussion->id, $istracked, $modulecontext); $usermapping = anonymous::get_userid_mapping($moodleoverflow, $discussion->id); @@ -985,7 +989,7 @@ function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discuss // Print the starting post. echo moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $course, - $ownpost, false, '', '', $postread, true, $istracked, 0, $usermapping, 0, $multiplemarks); + $ownpost, false, '', '', $postread, true, $istracked, 0, $usermapping, 0, $multiplemarks, $limitedanswersetting); // Print answer divider. if ($answercount == 1) { @@ -999,7 +1003,7 @@ function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discuss // Print the other posts. echo moodleoverflow_print_posts_nested($course, $cm, $moodleoverflow, $discussion, $post, $istracked, $posts, - null, $usermapping, $multiplemarks); + null, $usermapping, $multiplemarks, $limitedanswersetting); echo ''; } @@ -1060,7 +1064,7 @@ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking, $modc } // Load all ratings. - $discussionratings = \mod_moodleoverflow\ratings::moodleoverflow_get_ratings_by_discussion($discussionid); + $discussionratings = ratings::moodleoverflow_get_ratings_by_discussion($discussionid); // Assign ratings to the posts. foreach ($posts as $postid => $post) { @@ -1074,7 +1078,7 @@ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking, $modc } // Order the answers by their ratings. - $posts = \mod_moodleoverflow\ratings::moodleoverflow_sort_answers_by_ratings($posts); + $posts = ratings::moodleoverflow_sort_answers_by_ratings($posts); // Find all children of this post. foreach ($posts as $postid => $post) { @@ -1112,6 +1116,8 @@ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking, $modc /** + * + * LEARNWEB-TODO: REFACTOR THIS FUNCTION FOR THE NEW POST STRUCTURE. * Prints a moodleoverflow post. * @param object $post * @param object $discussion @@ -1129,6 +1135,7 @@ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking, $modc * @param array $usermapping * @param int $level * @param bool $multiplemarks setting of multiplemarks + * @param stdClass|null $limitedanswersetting * @return void|null * @throws coding_exception * @throws dml_exception @@ -1138,8 +1145,9 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $ownpost = false, $link = false, $footer = '', $highlight = '', $postisread = null, $dummyifcantsee = true, $istracked = false, - $iscomment = false, $usermapping = [], $level = 0, $multiplemarks = false) { - global $USER, $CFG, $OUTPUT, $PAGE; + $iscomment = false, $usermapping = [], $level = 0, + $multiplemarks = false, ?stdClass $limitedanswersetting = null) { + global $USER, $CFG, $OUTPUT, $PAGE, $DB; // Require the filelib. require_once($CFG->libdir . '/filelib.php'); @@ -1162,6 +1170,7 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $post->course = $course->id; $post->moodleoverflow = $moodleoverflow->id; $mcid = $modulecontext->id; + $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $mcid, 'mod_moodleoverflow', 'post', $post->id); // Check if the user has the capability to see posts. if (!moodleoverflow_user_can_see_post($moodleoverflow, $discussion, $post, $cm)) { @@ -1240,8 +1249,8 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $helpfulposts = false; $solvedposts = false; if ($multiplemarks) { - $helpfulposts = \mod_moodleoverflow\ratings::moodleoverflow_discussion_is_solved($discussion->id, false); - $solvedposts = \mod_moodleoverflow\ratings::moodleoverflow_discussion_is_solved($discussion->id, true); + $helpfulposts = ratings::moodleoverflow_discussion_is_solved($discussion->id, false); + $solvedposts = ratings::moodleoverflow_discussion_is_solved($discussion->id, true); } // If the user has started the discussion, he can mark the answer as helpful. @@ -1310,16 +1319,34 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co // Give the option to reply to a post. if (moodleoverflow_user_can_post($modulecontext, $post, false)) { - $attributes = [ 'class' => 'onlyifreviewed', ]; - // Answer to the parent post. if (empty($post->parent)) { - $replyurl = new moodle_url('/mod/moodleoverflow/post.php#mformmoodleoverflow', ['reply' => $post->id]); - $commands[] = ['url' => $replyurl, 'text' => $str->replyfirst, 'attributes' => $attributes]; - + // Check if limitedanswertime is on. + $settingexist = $limitedanswersetting->la_starttime != 0 || $limitedanswersetting->la_endtime != 0; + if ($settingexist) { + $infolimited = $limitedanswersetting->la_starttime ? " " . get_string('limitedanswer_info_starttime', + 'moodleoverflow', ['limitedanswerdate' => date('d.m.Y H:i', $limitedanswersetting->la_starttime)]) : ''; + $infolimited .= $limitedanswersetting->la_endtime ? " " . get_string('limitedanswer_info_endtime', 'moodleoverflow', + ['limitedanswerdate' => date('d.m.Y H:i', $limitedanswersetting->la_endtime)]) : ''; + echo html_writer::div($infolimited, 'alert alert-warning', ['role' => 'alert']); + } + if (is_currently_time_limited($limitedanswersetting)) { + if (!has_capability('mod/moodleoverflow:addinstance', $modulecontext)) { + // In case the user can not change the limited answer time he/she can not answer. + render_limited_answer('text-muted', $commands, $infolimited, 'student', $str->replyfirst); + } else { + // The user is a teacher. + $replyurl = new moodle_url('/mod/moodleoverflow/post.php#mformmoodleoverflow', ['reply' => $post->id]); + $answerbutton = html_writer::link($replyurl, $str->replyfirst, ['class' => 'onlyifreviewed answerbutton']); + render_limited_answer('', $commands, $infolimited, 'teacher', $answerbutton); + } + } else { + $replyurl = new moodle_url('/mod/moodleoverflow/post.php#mformmoodleoverflow', ['reply' => $post->id]); + $commands[] = ['url' => $replyurl, 'text' => $str->replyfirst, 'attributes' => $attributes]; + } // If the post is a comment, answer to the parent post. } else if (!$iscomment) { $replyurl = new moodle_url('/mod/moodleoverflow/post.php#mformmoodleoverflow', ['reply' => $post->id]); @@ -1349,7 +1376,7 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $mustachedata->markedsolution = $post->markedsolution; // Did the user rated this post? - $rating = \mod_moodleoverflow\ratings::moodleoverflow_user_rated($post->id); + $rating = ratings::moodleoverflow_user_rated($post->id); // Initiate the variables. $mustachedata->userupvoted = false; @@ -1423,7 +1450,7 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co if (anonymous::is_post_anonymous($discussion, $moodleoverflow, $post->userid)) { $postuserrating = null; } else { - $postuserrating = \mod_moodleoverflow\ratings::moodleoverflow_get_reputation($moodleoverflow->id, $postinguser->id); + $postuserrating = ratings::moodleoverflow_get_reputation($moodleoverflow->id, $postinguser->id); } // The name of the user and the date modified. @@ -1470,7 +1497,11 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $commandhtml = []; foreach ($commands as $command) { if (is_array($command)) { - $commandhtml[] = html_writer::link($command['url'], $command['text'], $command['attributes'] ?? null); + if (array_key_exists('limitedanswer', $command)) { + $commandhtml[] = html_writer::tag('span', $command['text'], $command['attributes'] ?? null); + } else { + $commandhtml[] = html_writer::link($command['url'], $command['text'], $command['attributes'] ?? null); + } } else { $commandhtml[] = $command; } @@ -1494,27 +1525,66 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co return $renderer->render_post($mustachedata); } +/** + * Check if the limited answer setting is currently disabling answers. + * @param stdClass $limitedanswersetting Two Unix timestamp wrapped in a stdClass, upper and lower label for answering. + * @return bool + */ +function is_currently_time_limited($limitedanswersetting): bool { + return ($limitedanswersetting->la_starttime != 0 && $limitedanswersetting->la_starttime > time()) + || ($limitedanswersetting->la_endtime != 0 && $limitedanswersetting->la_endtime < time()); +} + +/** + * Renders the answer action in a post. + * @param String $htmlattributes additional attributes passed to the html class and the command (either 'text-muted' or empty). + * @param array $commands array of actions available to the user in a post. + * @param String $infolimited information about the limited answer setting. + * @param String $role either 'student' or 'teacher'. + * @param String $helpstring content for the tag specifing the helpicon for the answer button. + * @return void + * @throws coding_exception + */ +function render_limited_answer($htmlattributes, &$commands, $infolimited, $role, $helpstring) { + $limitedanswerattributes = ['class' => 'onlyifreviewed ' . $htmlattributes]; + $htmlclass = 'onlyifreviewed helpicon ' . $htmlattributes; + $content = get_string('limitedanswer_info_start', 'moodleoverflow'); + $content .= $infolimited; + $htmlattributes == '' ? $content .= " " . get_string('limitedanswer_helpicon_teacher', 'moodleoverflow') : $content .= ''; + + $helpobject = new helpicon($htmlclass, $content); + $helpicon = $helpobject->get_helpicon(); + // Build a html span that has the answer button and the help icon. + $limitedanswerobject = html_writer::tag('span', $helpstring . ' ' . $helpicon); + + // Save the span in the commands with an extra value. + $commands[] = ['text' => $limitedanswerobject, + 'attributes' => $limitedanswerattributes, + 'limitedanswer' => $role, ]; +} /** * Prints all posts of the discussion in a nested form. * - * @param object $course The course object + * @param object $course The course object * @param object $cm - * @param object $moodleoverflow The moodleoverflow object - * @param object $discussion The discussion object - * @param object $parent The object of the parent post - * @param bool $istracked Whether the user tracks the discussion - * @param array $posts Array of posts within the discussion - * @param bool $iscomment Whether the current post is a comment - * @param array $usermapping - * @param bool $multiplemarks + * @param object $moodleoverflow The moodleoverflow object + * @param object $discussion The discussion object + * @param object $parent The object of the parent post + * @param bool $istracked Whether the user tracks the discussion + * @param array $posts Array of posts within the discussion + * @param bool $iscomment Whether the current post is a comment + * @param array $usermapping + * @param bool $multiplemarks The setting of multiplemarks (default: multiplemarks are not allowed) + * @param stdClass|null $limitedanswersetting Two Unix timestamp wrapped in a stdClass, upper and lower label for answering. * @return string * @throws coding_exception * @throws dml_exception * @throws moodle_exception */ function moodleoverflow_print_posts_nested($course, &$cm, $moodleoverflow, $discussion, $parent, - $istracked, $posts, $iscomment = null, $usermapping = [], $multiplemarks = false) { + $istracked, $posts, $iscomment = null, $usermapping = [], + $multiplemarks = false, ?stdClass $limitedanswersetting = null) { global $USER; // Prepare the output. @@ -1555,12 +1625,13 @@ function moodleoverflow_print_posts_nested($course, &$cm, $moodleoverflow, $disc $postread = !empty($post->postread); // Print the answer. - $output .= moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $course, - $ownpost, false, '', '', $postread, true, $istracked, $parentid, $usermapping, $level, $multiplemarks); + $output .= moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $course, $ownpost, false, '', '', + $postread, true, $istracked, $parentid, $usermapping, $level, + $multiplemarks, $limitedanswersetting); // Print its children. $output .= moodleoverflow_print_posts_nested($course, $cm, $moodleoverflow, - $discussion, $post, $istracked, $posts, $parentid, $usermapping, $multiplemarks); + $discussion, $post, $istracked, $posts, $parentid, $usermapping, $multiplemarks, $limitedanswersetting); // End the div. $output .= "\n"; @@ -1572,6 +1643,7 @@ function moodleoverflow_print_posts_nested($course, &$cm, $moodleoverflow, $disc } /** + * LEARNWEB-TODO: Delete this function after adapting the print_post function to the new post structure * Returns attachments with information for the template * * @param object $post @@ -1653,6 +1725,7 @@ function moodleoverflow_add_attachment($post, $forum, $cm) { } /** + * WARNING: this function is only used in the lib.php. For other uses this function is deprecated. * Adds a new post in an existing discussion. * @param object $post The post object * @return bool|int The Id of the post if operation was successful @@ -1709,93 +1782,6 @@ function moodleoverflow_add_new_post($post) { return $post->id; } -/** - * Updates a specific post. - * - * Capabilities are not checked, because this is happening in the post.php. - * - * @param object $newpost The new post object - * - * @return bool Whether the update was successful - */ -function moodleoverflow_update_post($newpost) { - global $DB, $USER; - - // Retrieve not submitted variables. - $post = $DB->get_record('moodleoverflow_posts', ['id' => $newpost->id]); - $discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $post->discussion]); - $moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $discussion->moodleoverflow]); - $cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id); - $context = context_module::instance($cm->id); - - // Allowed modifiable fields. - $modifiablefields = [ - 'message', - 'messageformat', - ]; - - // Iteratate through all modifiable fields and update the values. - foreach ($modifiablefields as $field) { - if (isset($newpost->{$field})) { - $post->{$field} = $newpost->{$field}; - } - } - - $post->modified = time(); - if ($newpost->reviewed ?? $post->reviewed) { - // Update the date and the user of the post and the discussion. - $discussion->timemodified = $post->modified; - $discussion->usermodified = $post->userid; - } - - // When editing the starting post of a discussion. - if (!$post->parent) { - $discussion->name = $newpost->subject; - } - - // Save draft files to permanent file area. - $post->message = file_save_draft_area_files($newpost->draftideditor, $context->id, 'mod_moodleoverflow', 'post', - $post->id, mod_forum_post_form::editor_options($context, $post->id), $post->message); - - // Update the post and the corresponding discussion. - $DB->update_record('moodleoverflow_posts', $post); - $DB->update_record('moodleoverflow_discussions', $discussion); - - $cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id); - moodleoverflow_add_attachment($newpost, $moodleoverflow, $cm); - - // Mark the edited post as read. - $cantrack = readtracking::moodleoverflow_can_track_moodleoverflows($moodleoverflow); - $istracked = readtracking::moodleoverflow_is_tracked($moodleoverflow); - if ($cantrack && $istracked) { - readtracking::moodleoverflow_mark_post_read($USER->id, $post); - } - - // The post has been edited successfully. - return true; -} - -/** - * Count all replies of a post. - * - * @param object $post The post object - * @param bool $onlyreviewed Whether to count only reviewed posts. - * - * @return int Amount of replies - */ -function moodleoverflow_count_replies($post, $onlyreviewed) { - global $DB; - - $conditions = ['parent' => $post->id]; - - if ($onlyreviewed) { - $conditions['reviewed'] = '1'; - } - - // Return the amount of replies. - return $DB->count_records('moodleoverflow_posts', $conditions); -} - /** * Deletes a discussion and handles all associated cleanups. * @@ -1924,6 +1910,7 @@ function moodleoverflow_delete_post($post, $deletechildren, $cm, $moodleoverflow } /** + * WARNING: this function is only used in the lib.php. For other uses this function is deprecated. * Sets the last post for a given discussion. * * @param int $discussionid The discussion ID @@ -2135,7 +2122,7 @@ function moodleoverflow_update_all_grades_for_cm($moodleoverflowid) { } // Get user reputation. - $userrating = \mod_moodleoverflow\ratings::moodleoverflow_get_reputation($moodleoverflow->id, $userid, true); + $userrating = ratings::moodleoverflow_get_reputation($moodleoverflow->id, $userid, true); // Calculate the posting user's updated grade. moodleoverflow_update_user_grade_on_db($moodleoverflow, $userrating, $userid); diff --git a/mod_form.php b/mod_form.php index 30cdef4367..987b98f23f 100644 --- a/mod_form.php +++ b/mod_form.php @@ -45,7 +45,7 @@ class mod_moodleoverflow_mod_form extends moodleform_mod { * Defines forms elements. */ public function definition() { - global $CFG, $COURSE, $PAGE; + global $CFG, $COURSE, $PAGE, $DB; // Define the modform. $mform = $this->_form; @@ -229,6 +229,45 @@ public function definition() { $mform->addHelpButton('allowmultiplemarks', 'allowmultiplemarks', 'moodleoverflow'); $mform->setDefault('allowmultiplemarks', 0); + // Limited answer options. + $mform->addElement('header', 'limitedanswerheading', get_string('limitedanswerheading', 'moodleoverflow')); + + $answersfound = false; + if (!empty($this->current->id)) { + // Check if there are already answered posts in this moodleoverflow and place a warning if so. + $sql = 'SELECT COUNT(*) AS answerposts + FROM {moodleoverflow_discussions} discuss JOIN {moodleoverflow_posts} posts + ON discuss.id = posts.discussion + WHERE posts.parent != 0 + AND discuss.moodleoverflow = ' . $this->current->id . ';'; + $answerpostscount = $DB->get_records_sql($sql); + $answerpostscount = $answerpostscount[array_key_first($answerpostscount)]->answerposts; + $answersfound = $answerpostscount > 0; + if ($answersfound) { + $warningstring = get_string('limitedanswerwarning_answers', 'moodleoverflow'); + $warningstring .= '
' . get_string('limitedanswerwarning_conclusion', 'moodleoverflow'); + $htmlwarning = html_writer::div($warningstring, 'alert alert-warning', ['role' => 'alert']); + $mform->addElement('html', $htmlwarning); + } + } + + // Limited answer setting elements.. + $mform->addElement('hidden', 'la_answersfound', $answersfound); + $mform->setType('la_answersfound', PARAM_BOOL); + $mform->addElement('date_time_selector', 'la_starttime', get_string('la_starttime', 'moodleoverflow'), + ['optional' => true]); + + $mform->addHelpButton('la_starttime', 'la_starttime', 'moodleoverflow'); + $mform->disabledIf('la_starttime', 'la_answersfound', 'eq', true); + + $mform->addElement('date_time_selector', 'la_endtime', get_string('la_endtime', 'moodleoverflow'), + ['optional' => true]); + + $mform->addHelpButton('la_endtime', 'la_endtime', 'moodleoverflow'); + + $mform->addElement('hidden', 'la_error'); + $mform->setType('la_error', PARAM_TEXT); + // Add standard elements, common to all modules. $this->standard_coursemodule_elements(); @@ -249,4 +288,39 @@ public function data_postprocessing($data) { $data->coursewidereputation = false; } } + + /** + * Validates set data in mod_form + * @param array $data + * @param array $files + * @return array + * @throws coding_exception + */ + public function validation($data, $files): array { + $errors = parent::validation($data, $files); + + // Validate that the limited answer settings. + $currenttime = time(); + $isstarttime = !empty($data['la_starttime']); + $isendtime = !empty($data['la_endtime']); + + if ($isstarttime && $data['la_starttime'] < $currenttime) { + $errors['la_starttime'] = get_string('la_starttime_ruleerror', 'moodleoverflow'); + } + if ($isendtime) { + if ($data['la_endtime'] < $currenttime) { + $errors['la_endtime'] = get_string('la_endtime_ruleerror', 'moodleoverflow'); + } + + if ($isstarttime && $data['la_endtime'] <= $data['la_starttime']) { + if (isset($errors['la_endtime'])) { + $errors['la_endtime'] .= '
' . get_string('la_sequence_error', 'moodleoverflow'); + } else { + $errors['la_endtime'] = get_string('la_sequence_error', 'moodleoverflow'); + } + } + } + + return $errors; + } } diff --git a/pix/monologo.svg b/pix/monologo.svg index a6ea2dcfcd..66041b0217 100644 --- a/pix/monologo.svg +++ b/pix/monologo.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/post.php b/post.php index 86bf67b23c..43caa1e6db 100644 --- a/post.php +++ b/post.php @@ -15,13 +15,14 @@ // along with Moodle. If not, see . /** - * The file to manage posts. + * The file that is opened in Moodle when the user interacts with posts * * @package mod_moodleoverflow - * @copyright 2017 Kennet Winter + * @copyright 2023 Tamaro Walter * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use mod_moodleoverflow\post\post_control; // Include config and locallib. use mod_moodleoverflow\anonymous; use mod_moodleoverflow\review; @@ -31,31 +32,16 @@ require_once(dirname(__FILE__) . '/locallib.php'); require_once($CFG->libdir . '/completionlib.php'); -// Declare optional parameters. +// Declare optional url parameters. $moodleoverflow = optional_param('moodleoverflow', 0, PARAM_INT); $reply = optional_param('reply', 0, PARAM_INT); $edit = optional_param('edit', 0, PARAM_INT); $delete = optional_param('delete', 0, PARAM_INT); $confirm = optional_param('confirm', 0, PARAM_INT); -$count = 0; -$count += $moodleoverflow ? 1 : 0; -$count += $reply ? 1 : 0; -$count += $edit ? 1 : 0; -$count += $delete ? 1 : 0; - -if ($count !== 1) { - throw new coding_exception('Exactly one parameter should be specified!'); -} - // Set the URL that should be used to return to this page. -$PAGE->set_url('/mod/moodleoverflow/post.php', [ - 'moodleoverflow' => $moodleoverflow, - 'reply' => $reply, - 'edit' => $edit, - 'delete' => $delete, - 'confirm' => $confirm, -]); +$PAGE->set_url('/mod/moodleoverflow/post.php', ['moodleoverflow' => $moodleoverflow, 'reply' => $reply, 'edit' => $edit, + 'delete' => $delete, 'confirm' => $confirm, ]); // These params will be passed as hidden variables later in the form. $pageparams = ['moodleoverflow' => $moodleoverflow, 'reply' => $reply, 'edit' => $edit]; @@ -63,739 +49,105 @@ // Get the system context instance. $systemcontext = context_system::instance(); -// Catch guests. -if (!isloggedin() || isguestuser()) { - - // The user is starting a new discussion in a moodleoverflow instance. - if (!empty($moodleoverflow)) { - - // Check the moodleoverflow instance is valid. - if (!$moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $moodleoverflow])) { - throw new moodle_exception('invalidmoodleoverflowid', 'moodleoverflow'); - } - - // The user is replying to an existing moodleoverflow discussion. - } else if (!empty($reply)) { - - // Check if the related post exists. - if (!$parent = moodleoverflow_get_post_full($reply)) { - throw new moodle_exception('invalidparentpostid', 'moodleoverflow'); - } - - // Check if the post is part of a valid discussion. - if (!$discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $parent->discussion])) { - throw new moodle_exception('notpartofdiscussion', 'moodleoverflow'); - } - - // Check if the post is related to a valid moodleoverflow instance. - if (!$moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $discussion->moodleoverflow])) { - throw new moodle_exception('invalidmoodleoverflowid', 'moodleoverflow'); - } - } - - // Get the related course. - if (!$course = $DB->get_record('course', ['id' => $moodleoverflow->course])) { - throw new moodle_exception('invalidcourseid'); - } - - // Get the related coursemodule and its context. - if (!$cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id, $course->id)) { - throw new moodle_exception('invalidcoursemodule'); - } +// Create a post_control object to control and lead the process. +$postcontrol = new post_control(); - // Get the context of the module. - $modulecontext = context_module::instance($cm->id); +// Put all interaction parameters in one object for the post_control. +$urlparameter = new stdClass(); +$urlparameter->create = $moodleoverflow; +$urlparameter->reply = $reply; +$urlparameter->edit = $edit; +$urlparameter->delete = $delete; - // Set parameters for the page. - $PAGE->set_cm($cm, $course, $moodleoverflow); - $PAGE->set_context($modulecontext); - $PAGE->set_title($course->shortname); - $PAGE->set_heading($course->fullname); - - // The page should not be large, only pages containing broad tables are usually. - $PAGE->add_body_class('limitedwidth'); +// Catch guests. +if (!isloggedin() || isguestuser()) { + // Gather information and set the page right so that user can be redirected to the right site. + $information = $postcontrol->catch_guest(); // The guest needs to login. - echo $OUTPUT->header(); $strlogin = get_string('noguestpost', 'forum') . '

' . get_string('liketologin'); - echo $OUTPUT->confirm($strlogin, get_login_url(), $CFG->wwwroot . '/mod/moodleoverflow/view.php?m=' . $moodleoverflow->id); + echo $OUTPUT->header(); + echo $OUTPUT->confirm($strlogin, get_login_url(), + $CFG->wwwroot . '/mod/moodleoverflow/view.php?m= ' . $information->moodleoverflow->id); echo $OUTPUT->footer(); exit; } -// First step: A general login is needed to post something. +// Require a general login to post something. +// LEARNWEB-TODO: should course or id really be zero?. require_login(0, false); -// First possibility: User is starting a new discussion in a moodleoverflow instance. -if (!empty($moodleoverflow)) { - - // Check the moodleoverflow instance is valid. - if (!$moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $moodleoverflow])) { - throw new moodle_exception('invalidmoodleoverflowid', 'moodleoverflow'); - } - - // Get the related course. - if (!$course = $DB->get_record('course', ['id' => $moodleoverflow->course])) { - throw new moodle_exception('invalidcourseid'); - } - - // Get the related coursemodule. - if (!$cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id, $course->id)) { - throw new moodle_exception('invalidcoursemodule'); - } - - // Retrieve the contexts. - $modulecontext = context_module::instance($cm->id); - $coursecontext = context_course::instance($course->id); - - // Check if the user can start a new discussion. - if (!moodleoverflow_user_can_post_discussion($moodleoverflow, $cm, $modulecontext)) { - - // Catch unenrolled user. - if (!isguestuser() && !is_enrolled($coursecontext)) { - if (enrol_selfenrol_available($course->id)) { - $SESSION->wantsurl = qualified_me(); - $SESSION->enrolcancel = get_local_referer(false); - redirect(new moodle_url('/enrol/index.php', [ - 'id' => $course->id, - 'returnurl' => '/mod/moodleoverflow/view.php?m=' . $moodleoverflow->id, - ]), get_string('youneedtoenrol')); - } - } - - // Notify the user, that he can not post a new discussion. - throw new moodle_exception('nopostmoodleoverflow', 'moodleoverflow'); - } - - // Where is the user coming from? - $SESSION->fromurl = get_local_referer(false); - - // Load all the $post variables. - $post = new stdClass(); - $post->course = $course->id; - $post->moodleoverflow = $moodleoverflow->id; - $post->discussion = 0; - $post->parent = 0; - $post->subject = ''; - $post->userid = $USER->id; - $post->message = ''; - - // Unset where the user is coming from. - // Allows to calculate the correct return url later. - unset($SESSION->fromdiscussion); - -} else if (!empty($reply)) { - // Second possibility: The user is writing a new reply. - - // Check if the post exists. - if (!$parent = moodleoverflow_get_post_full($reply)) { - throw new moodle_exception('invalidparentpostid', 'moodleoverflow'); - } - - // Check if the post is part of a discussion. - if (!$discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $parent->discussion])) { - throw new moodle_exception('notpartofdiscussion', 'moodleoverflow'); - } - - // Check if the discussion is part of a moodleoverflow instance. - if (!$moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $discussion->moodleoverflow])) { - throw new moodle_exception('invalidmoodleoverflowid', 'moodleoverflow'); - } - - // Check if the moodleoverflow instance is part of a course. - if (!$course = $DB->get_record('course', ['id' => $discussion->course])) { - throw new moodle_exception('invalidcourseid'); - } - - // Retrieve the related coursemodule. - if (!$cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id, $course->id)) { - throw new moodle_exception('invalidcoursemodule'); - } - - // Ensure the coursemodule is set correctly. - $PAGE->set_cm($cm, $course, $moodleoverflow); - - // Retrieve the other contexts. - $modulecontext = context_module::instance($cm->id); - $coursecontext = context_course::instance($course->id); - - // Check whether the user is allowed to post. - if (!moodleoverflow_user_can_post($modulecontext, $parent)) { - - // Give the user the chance to enroll himself to the course. - if (!isguestuser() && !is_enrolled($coursecontext)) { - $SESSION->wantsurl = qualified_me(); - $SESSION->enrolcancel = get_local_referer(false); - redirect(new moodle_url('/enrol/index.php', - ['id' => $course->id, 'returnurl' => '/mod/moodleoverflow/view.php?m=' . $moodleoverflow->id]), - get_string('youneedtoenrol')); - } - - // Print the error message. - throw new moodle_exception('nopostmoodleoverflow', 'moodleoverflow'); - } - - // Make sure the user can post here. - if (!$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $modulecontext)) { - throw new moodle_exception('activityiscurrentlyhidden'); - } - - // Load the $post variable. - $post = new stdClass(); - $post->course = $course->id; - $post->moodleoverflow = $moodleoverflow->id; - $post->discussion = $parent->discussion; - $post->parent = $parent->id; - $post->subject = $discussion->name; - $post->userid = $USER->id; - $post->message = ''; - - // Append 'RE: ' to the discussions subject. - $strre = get_string('re', 'moodleoverflow'); - if (!(substr($post->subject, 0, strlen($strre)) == $strre)) { - $post->subject = $strre . ' ' . $post->subject; - } - - // Unset where the user is coming from. - // Allows to calculate the correct return url later. - unset($SESSION->fromdiscussion); - - -} else if (!empty($edit)) { - // Third possibility: The user is editing his own post. - - // Check if the submitted post exists. - if (!$post = moodleoverflow_get_post_full($edit)) { - throw new moodle_exception('invalidpostid', 'moodleoverflow'); - } - - // Get the parent post of this post if it is not the starting post of the discussion. - if ($post->parent) { - if (!$parent = moodleoverflow_get_post_full($post->parent)) { - throw new moodle_exception('invalidparentpostid', 'moodleoverflow'); - } - } - - // Check if the post refers to a valid discussion. - if (!$discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $post->discussion])) { - throw new moodle_exception('notpartofdiscussion', 'moodleoverflow'); - } - - // Check if the post refers to a valid moodleoverflow instance. - if (!$moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $discussion->moodleoverflow])) { - throw new moodle_exception('invalidmoodleoverflowid', 'moodleoverflow'); - } - - // Check if the post refers to a valid course. - if (!$course = $DB->get_record('course', ['id' => $discussion->course])) { - throw new moodle_exception('invalidcourseid'); - } - - // Retrieve the related coursemodule. - if (!$cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id, $course->id)) { - throw new moodle_exception('invalidcoursemodule'); - } else { - $modulecontext = context_module::instance($cm->id); - } - - // Set the pages context. - $PAGE->set_cm($cm, $course, $moodleoverflow); - - // Check if the post can be edited. - $beyondtime = ((time() - $post->created) > get_config('moodleoverflow', 'maxeditingtime')); - $alreadyreviewed = review::should_post_be_reviewed($post, $moodleoverflow) && $post->reviewed; - if (($beyondtime || $alreadyreviewed) && !has_capability('mod/moodleoverflow:editanypost', $modulecontext)) { - throw new moodle_exception('maxtimehaspassed', 'moodleoverflow', '', - format_time(get_config('moodleoverflow', 'maxeditingtime'))); - } - - - - // If the current user is not the one who posted this post. - if ($post->userid <> $USER->id) { - - // Check if the current user has not the capability to edit any post. - if (!has_capability('mod/moodleoverflow:editanypost', $modulecontext)) { - - // Display the error. Capabilities are missing. - throw new moodle_exception('cannoteditposts', 'moodleoverflow'); - } - } - - // Load the $post variable. - $post->edit = $edit; - $post->course = $course->id; - $post->moodleoverflow = $moodleoverflow->id; - - // Unset where the user is coming from. - // Allows to calculate the correct return url later. - unset($SESSION->fromdiscussion); - -} else if (!empty($delete)) { - // Fourth possibility: The user is deleting a post. - // Check if the post is existing. - if (!$post = moodleoverflow_get_post_full($delete)) { - throw new moodle_exception('invalidpostid', 'moodleoverflow'); - } - - // Get the related discussion. - if (!$discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $post->discussion])) { - throw new moodle_exception('notpartofdiscussion', 'moodleoverflow'); - } - - // Get the related moodleoverflow instance. - if (!$moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $discussion->moodleoverflow])) { - throw new moodle_exception('invalidmoodleoverflowid', 'moodleoveflow'); - } - - // Get the related coursemodule. - if (!$cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id, $moodleoverflow->course)) { - throw new moodle_exception('invalidcoursemodule'); - } - - // Get the related course. - if (!$course = $DB->get_record('course', ['id' => $moodleoverflow->course])) { - throw new moodle_exception('invalidcourseid'); - } - - // Require a login and retrieve the modulecontext. - require_login($course, false, $cm); - $modulecontext = context_module::instance($cm->id); - - // Check some capabilities. - $deleteownpost = has_capability('mod/moodleoverflow:deleteownpost', $modulecontext); - $deleteanypost = has_capability('mod/moodleoverflow:deleteanypost', $modulecontext); - if (!(($post->userid == $USER->id && $deleteownpost) || $deleteanypost)) { - throw new moodle_exception('cannotdeletepost', 'moodleoverflow'); - } - - // Count all replies of this post. - $replycount = moodleoverflow_count_replies($post, false); +// Now the post_control checks which interaction is wanted and builds a prepost. +$postcontrol->detect_interaction($urlparameter); +// If a post is being deleted, delete it immediately. +if ($postcontrol->get_interaction() == 'delete') { // Has the user confirmed the deletion? if (!empty($confirm) && confirm_sesskey()) { - - // Check if the user has the capability to delete the post. - $timepassed = time() - $post->created; - if (($timepassed > get_config('moodleoverflow', 'maxeditingtime')) && !$deleteanypost) { - $url = new moodle_url('/mod/moodleoverflow/discussion.php', ['d' => $post->discussion]); - throw new moodle_exception('cannotdeletepost', 'moodleoverflow', moodleoverflow_go_back_to($url)); - } - - // A normal user cannot delete his post if there are direct replies. - if ($replycount && !$deleteanypost) { - $url = new moodle_url('/mod/moodleoverflow/discussion.php', ['d' => $post->discussion]); - throw new moodle_exception('couldnotdeletereplies', 'moodleoverflow', moodleoverflow_go_back_to($url)); - } else { - // Delete the post. - - // The post is the starting post of a discussion. Delete the topic as well. - if (!$post->parent) { - moodleoverflow_delete_discussion($discussion, $course, $cm, $moodleoverflow); - - // Trigger the discussion deleted event. - $params = [ - 'objectid' => $discussion->id, - 'context' => $modulecontext, - ]; - - $event = \mod_moodleoverflow\event\discussion_deleted::create($params); - $event->trigger(); - - // Redirect the user back to start page of the moodleoverflow instance. - redirect("view.php?m=$discussion->moodleoverflow"); - exit; - - } else if (moodleoverflow_delete_post($post, $deleteanypost, $cm, $moodleoverflow)) { - // Delete a single post. - // Redirect back to the discussion. - $discussionurl = new moodle_url('/mod/moodleoverflow/discussion.php', ['d' => $discussion->id]); - redirect(moodleoverflow_go_back_to($discussionurl)); - exit; - - } else { - // Something went wrong. - throw new moodle_exception('errorwhiledelete', 'moodleoverflow'); - } - } + $postcontrol->execute_delete(); } else { // Deletion needs to be confirmed. - - moodleoverflow_set_return(); - $PAGE->navbar->add(get_string('delete', 'moodleoverflow')); - $PAGE->set_title($course->shortname); - $PAGE->set_heading($course->fullname); - - // The page should not be large, only pages containing broad tables are usually. - $PAGE->add_body_class('limitedwidth'); - - // Check if there are replies for the post. - if ($replycount) { - - // Check if the user has capabilities to delete more than one post. - if (!$deleteanypost) { - throw new moodle_exception('couldnotdeletereplies', 'moodleoverflow', - moodleoverflow_go_back_to(new moodle_url('/mod/moodleoverflow/discussion.php', - ['d' => $post->discussion, 'p' . $post->id]))); - } - - // Request a confirmation to delete the post. - echo $OUTPUT->header(); - echo $OUTPUT->confirm(get_string("deletesureplural", "moodleoverflow", $replycount + 1), - "post.php?delete=$delete&confirm=$delete", $CFG->wwwroot . '/mod/moodleoverflow/discussion.php?d=' . - $post->discussion . '#p' . $post->id); - + $postcontrol->confirm_delete(); + + // Display a confirmation request depending on the number of posts that are being deleted. + $information = $postcontrol->get_information(); + echo $OUTPUT->header(); + if ($information->deletetype == 'plural') { + echo $OUTPUT->confirm(get_string('deletesureplural', 'moodleoverflow', $information->replycount + 1), + 'post.php?delete='.$delete.'&confirm='.$delete, + $CFG->wwwroot . '/mod/moodleoverflow/discussion.php?d=' . $information->discussion->get_id() . + '#p' . $information->relatedpost->get_id()); } else { - // Delete a single post. - - // Print a confirmation message. - echo $OUTPUT->header(); - echo $OUTPUT->confirm(get_string("deletesure", "moodleoverflow", $replycount), + echo $OUTPUT->confirm(get_string('deletesure', 'moodleoverflow', $information->replycount), "post.php?delete=$delete&confirm=$delete", - $CFG->wwwroot . '/mod/moodleoverflow/discussion.php?d=' . $post->discussion . '#p' . $post->id); + $CFG->wwwroot . '/mod/moodleoverflow/discussion.php?d=' . $information->discussion->get_id() . + '#p' . $information->relatedpost->get_id()); } + echo $OUTPUT->footer(); } - echo $OUTPUT->footer(); exit; - -} else { - // Last posibility: the action is not known. - - throw new moodle_exception('unknownaction'); } -// Second step: The user must be logged on properly. Must be enrolled to the course as well. -require_login($course, false, $cm); - -// Get the contexts. -$modulecontext = context_module::instance($cm->id); -$coursecontext = context_course::instance($course->id); - -// Get the subject. -if ($edit) { - $subject = $discussion->name; -} else if ($reply) { - $subject = $post->subject; -} else if ($moodleoverflow) { - $subject = $post->subject; -} +// A post will be created or edited. For that the post_control builds a post_form. +$mformpost = $postcontrol->build_postform($pageparams); -// Get attachments. -$postid = empty($post->id) ? null : $post->id; -$draftitemid = file_get_submitted_draft_itemid('attachments'); -file_prepare_draft_area($draftitemid, - $modulecontext->id, - 'mod_moodleoverflow', - 'attachment', - $postid, - mod_moodleoverflow_post_form::attachment_options($moodleoverflow)); +// The User now entered information in the form. The post.php now needs to process the information and call the right function. -if ($draftitemid && $edit && anonymous::is_post_anonymous($discussion, $moodleoverflow, $post->userid) - && $post->userid != $USER->id) { - - $usercontext = context_user::instance($USER->id); - $anonymousstr = get_string('anonymous', 'moodleoverflow'); - foreach (get_file_storage()->get_area_files($usercontext->id, 'user', 'draft', $draftitemid) as $file) { - $file->set_author($anonymousstr); - } -} +// Get attributes from the postcontrol. +$information = $postcontrol->get_information(); +$prepost = $postcontrol->get_prepost(); -$draftideditor = file_get_submitted_draft_itemid('message'); -$currenttext = file_prepare_draft_area($draftideditor, $modulecontext->id, 'mod_moodleoverflow', 'post', $postid, - mod_moodleoverflow_post_form::editor_options($modulecontext, $postid), $post->message); - -// Prepare the form. -$formarray = [ - 'course' => $course, - 'cm' => $cm, - 'coursecontext' => $coursecontext, - 'modulecontext' => $modulecontext, - 'moodleoverflow' => $moodleoverflow, - 'post' => $post, - 'edit' => $edit, -]; -$mformpost = new mod_moodleoverflow_post_form('post.php', $formarray, 'post', '', ['id' => 'mformmoodleoverflow']); - -// The current user is not the original author. -// Append the message to the end of the message. -if ($USER->id != $post->userid) { - - // Create a temporary object. - $data = new stdClass(); - $data->date = userdate($post->modified); - $post->messageformat = editors_get_preferred_format(); - - // Append the message depending on the messages format. - if ($post->messageformat == FORMAT_HTML) { - $data->name = '' . fullname($USER) . ''; - $post->message .= '

(' . get_string('editedby', 'moodleoverflow', $data) . ')

'; - } else { - $data->name = fullname($USER); - $post->message .= "\n\n(" . get_string('editedby', 'moodleoverflow', $data) . ')'; - } - - // Delete the temporary object. - unset($data); -} - -// Define the heading for the form. -$formheading = ''; -if (!empty($parent)) { - $heading = get_string('yourreply', 'moodleoverflow'); - $formheading = get_string('reply', 'moodleoverflow'); -} else { - $heading = get_string('yournewtopic', 'moodleoverflow'); -} - -// Set data for the form. -// TODO Refactor. -$param1 = (isset($discussion->id) ? [$discussion->id] : []); -$param2 = (isset($post->format) ? ['format' => $post->format] : []); -$param3 = (isset($discussion->timestart) ? ['timestart' => $discussion->timestart] : []); -$param4 = (isset($discussion->timeend) ? ['timeend' => $discussion->timeend] : []); -$param5 = (isset($discussion->id) ? ['discussion' => $discussion->id] : []); -$mformpost->set_data([ - 'attachments' => $draftitemid, - 'general' => $heading, - 'subject' => $subject, - 'message' => [ - 'text' => $currenttext, - 'format' => editors_get_preferred_format(), - 'itemid' => $draftideditor, - ], - 'userid' => $post->userid, - 'parent' => $post->parent, - 'discussion' => $post->discussion, - 'course' => $course->id, - ] + $pageparams + $param1 + $param2 + $param3 + $param4 + $param5); - -// Is it canceled? +// If the interaction was cancelled, the user needs to be redirected. if ($mformpost->is_cancelled()) { - - // Redirect the user back. - if (!isset($discussion->id)) { - redirect(new moodle_url('/mod/moodleoverflow/view.php', ['m' => $moodleoverflow->id])); + if (!isset($prepost->discussionid)) { + redirect(new moodle_url('/mod/moodleoverflow/view.php', ['m' => $prepost->moodleoverflowid])); } else { - redirect(new moodle_url('/mod/moodleoverflow/discussion.php', ['d' => $discussion->id])); + redirect(new moodle_url('/mod/moodleoverflow/discussion.php', ['d' => $prepost->discussionid])); } - - // Cancel. - exit(); + exit; } -// Is it submitted? +// If the post_form is submitted, the post_control executes the right function. if ($fromform = $mformpost->get_data()) { - - // Redirect url in case of occuring errors. - if (empty($SESSION->fromurl)) { - $errordestination = "$CFG->wwwroot/mod/moodleoverflow/view.php?m=$moodleoverflow->id"; - } else { - $errordestination = $SESSION->fromurl; - } - - // Format the submitted data. - $fromform->messageformat = $fromform->message['format']; - $fromform->draftideditor = $fromform->message['itemid']; - $fromform->message = $fromform->message['text']; - $fromform->messagetrust = trusttext_trusted($modulecontext); - - // If we are updating a post. - if ($fromform->edit) { - - // Initiate some variables. - unset($fromform->groupid); - $fromform->id = $fromform->edit; - $message = ''; - - // The FORUM-Plugin had an bug: https://tracker.moodle.org/browse/MDL-4314 - // This is a fix for it. - if (!$realpost = $DB->get_record('moodleoverflow_posts', ['id' => $fromform->id])) { - $realpost = new stdClass(); - $realpost->userid = -1; - } - - // Check the capabilities of the user. - // He may proceed if he can edit any post or if he has the startnewdiscussion - // capability or the capability to reply and is editing his own post. - $editanypost = has_capability('mod/moodleoverflow:editanypost', $modulecontext); - $replypost = has_capability('mod/moodleoverflow:replypost', $modulecontext); - $startdiscussion = has_capability('mod/moodleoverflow:startdiscussion', $modulecontext); - $ownpost = ($realpost->userid == $USER->id); - if (!(($ownpost && ($replypost || $startdiscussion)) || $editanypost)) { - throw new moodle_exception('cannotupdatepost', 'moodleoverflow'); - } - - // Update the post or print an error message. - $updatepost = $fromform; - $updatepost->moodleoverflow = $moodleoverflow->id; - if (!moodleoverflow_update_post($updatepost, $mformpost)) { - throw new moodle_exception('couldnotupdate', 'moodleoverflow', $errordestination); - } - - // Create a success-message. - if ($realpost->userid == $USER->id) { - $message .= get_string('postupdated', 'moodleoverflow'); - } else { - if (anonymous::is_post_anonymous($discussion, $moodleoverflow, $realpost->userid)) { - $name = get_string('anonymous', 'moodleoverflow'); - } else { - $realuser = $DB->get_record('user', ['id' => $realpost->userid]); - $name = fullname($realuser); - } - $message .= get_string('editedpostupdated', 'moodleoverflow', $name); - } - - // Create a link to go back to the discussion. - $discussionurl = new moodle_url('/mod/moodleoverflow/discussion.php', ['d' => $discussion->id], 'p' . $fromform->id); - - // Set some parameters. - $params = [ - 'context' => $modulecontext, - 'objectid' => $fromform->id, - 'other' => [ - 'discussionid' => $discussion->id, - 'moodleoverflowid' => $moodleoverflow->id, - ], ]; - - // If the editing user is not the original author, add the original author to the params. - if ($realpost->userid != $USER->id) { - $params['relateduserid'] = $realpost->userid; - } - - // Trigger post updated event. - $event = \mod_moodleoverflow\event\post_updated::create($params); - $event->trigger(); - - // Redirect back to the discussion. - redirect(moodleoverflow_go_back_to($discussionurl), $message, null, \core\output\notification::NOTIFY_SUCCESS); - - // Cancel. - exit; - - } else if ($fromform->discussion) { - // Add a new post to an existing discussion. - - // Set some basic variables. - unset($fromform->groupid); - $message = ''; - $addpost = $fromform; - $addpost->moodleoverflow = $moodleoverflow->id; - - // Create the new post. - if ($fromform->id = moodleoverflow_add_new_post($addpost)) { - - // Subscribe to this thread. - $discussion = new \stdClass(); - $discussion->id = $fromform->discussion; - $discussion->moodleoverflow = $moodleoverflow->id; - \mod_moodleoverflow\subscriptions::moodleoverflow_post_subscription($moodleoverflow, $discussion, $modulecontext); - - // Print a success-message. - $message .= '

' . get_string("postaddedsuccess", "moodleoverflow") . '

'; - $message .= '

' . get_string("postaddedtimeleft", "moodleoverflow", - format_time(get_config('moodleoverflow', 'maxeditingtime'))) . '

'; - - // Set the URL that links back to the discussion. - $link = '/mod/moodleoverflow/discussion.php'; - $discussionurl = new moodle_url($link, ['d' => $discussion->id], 'p' . $fromform->id); - - // Trigger post created event. - $params = [ - 'context' => $modulecontext, - 'objectid' => $fromform->id, - 'other' => [ - 'discussionid' => $discussion->id, - 'moodleoverflowid' => $moodleoverflow->id, - ], ]; - $event = \mod_moodleoverflow\event\post_created::create($params); - $event->trigger(); - redirect( - moodleoverflow_go_back_to($discussionurl), - $message, - \core\output\notification::NOTIFY_SUCCESS - ); - - // Print an error if the answer could not be added. - } else { - throw new moodle_exception('couldnotadd', 'moodleoverflow', $errordestination); - } - - // The post has been added. - exit; - - } else { - // Add a new discussion. - - // The location to redirect the user after successfully posting. - $redirectto = new moodle_url('view.php', ['m' => $fromform->moodleoverflow]); - - $discussion = $fromform; - $discussion->name = $fromform->subject; - - // Check if the user is allowed to post here. - if (!moodleoverflow_user_can_post_discussion($moodleoverflow)) { - throw new moodle_exception('cannotcreatediscussion', 'moodleoverflow'); - } - - // Check if the creation of the new discussion failed. - if (!$discussion->id = moodleoverflow_add_discussion($discussion, $modulecontext)) { - - throw new moodle_exception('couldnotadd', 'moodleoverflow', $errordestination); - - } else { // The creation of the new discussion was successful. - - $params = [ - 'context' => $modulecontext, - 'objectid' => $discussion->id, - 'other' => [ - 'moodleoverflowid' => $moodleoverflow->id, - ], - ]; - - $message = '

' . get_string("postaddedsuccess", "moodleoverflow") . '

'; - - // Trigger the discussion created event. - $params = [ - 'context' => $modulecontext, - 'objectid' => $discussion->id, - ]; - $event = \mod_moodleoverflow\event\discussion_created::create($params); - $event->trigger(); - // Subscribe to this thread. - $discussion->moodleoverflow = $moodleoverflow->id; - \mod_moodleoverflow\subscriptions::moodleoverflow_post_subscription($moodleoverflow, $discussion, $modulecontext); - } - - // Redirect back to te discussion. - redirect(moodleoverflow_go_back_to($redirectto->out()), $message, null, \core\output\notification::NOTIFY_SUCCESS); - - // Do not continue. - exit; - } + $postcontrol->execute_interaction($fromform); + exit; } // If the script gets to this point, nothing has been submitted. -// We have to display the form. -// $course and $moodleoverflow are defined. -// $discussion is only used for replying and editing. +// The post_form will be displayed. // Define the message to be displayed above the form. $toppost = new stdClass(); -$toppost->subject = get_string("addanewdiscussion", "moodleoverflow"); +$toppost->subject = get_string('addanewdiscussion', 'moodleoverflow'); // Initiate the page. -$PAGE->set_title("$course->shortname: $moodleoverflow->name " . format_string($toppost->subject)); -$PAGE->set_heading($course->fullname); - -// The page should not be large, only pages containing broad tables are usually. +$PAGE->set_title($information->course->shortname . ': ' . + $information->moodleoverflow->name . ' ' . + format_string($toppost->subject)); +$PAGE->set_heading($information->course->fullname); $PAGE->add_body_class('limitedwidth'); -// Display the header. +// Display all. echo $OUTPUT->header(); - -// Display the form. $mformpost->display(); - -// Display the footer. echo $OUTPUT->footer(); diff --git a/templates/discussion_list.mustache b/templates/discussion_list.mustache index 1cb4a05c76..280097fe8d 100644 --- a/templates/discussion_list.mustache +++ b/templates/discussion_list.mustache @@ -35,16 +35,16 @@ {{! There are discussions. Start to print the table. }} {{#hasdiscussions}} - +
{{! Print the heading row of the table. }} - {{# canreview }} + {{#canreview}} - {{/ canreview }} + {{/canreview}} - {{# canreview }} + {{#canreview}} - {{/ canreview }} + {{/canreview}} {{! The status of the discussion. }} {{! The votes of the discussion. }} @@ -168,7 +168,7 @@ {{ unreadamount }} - {{# pix}} t/markasread, core, {{#str}}markread, moodleoverflow{{/str}} {{/ pix}} + {{#pix}} t/markasread, core, {{#str}}markread, moodleoverflow{{/str}} {{/pix}} {{/unread}} @@ -194,12 +194,12 @@ diff --git a/templates/discussions.mustache b/templates/discussions.mustache index 96d87a246a..11d78abe75 100644 --- a/templates/discussions.mustache +++ b/templates/discussions.mustache @@ -27,7 +27,7 @@ {{! There are no discussions. Print the string that specifies it. }} {{^hasdiscussions}} -
+
( {{#str}} nodiscussions, moodleoverflow {{/str}} )
{{/hasdiscussions}} @@ -42,33 +42,33 @@ {{> mod_moodleoverflow/postvoting }}
- {{# questionunderreview }} + {{#questionunderreview}} {{#pix}}i/duration, moodle, {{#str}}pending_review, mod_moodleoverflow{{/str}}{{/pix}} - {{/ questionunderreview }} - {{^ questionunderreview }} + {{/questionunderreview}} + {{^questionunderreview}} {{#markedsolution}} {{! avoid whitespace - !}}{{# pix}} i/status-solved, moodleoverflow, {{#str}}containsteacherrating, moodleoverflow{{/str}} {{/ pix}}{{! + !}}{{#pix}} i/status-solved, moodleoverflow, {{#str}}containsteacherrating, moodleoverflow{{/str}} {{/pix}}{{! !}} {{/markedsolution}} {{#markedhelpful}} {{! avoid whitespace - !}}{{# pix}} i/status-helpful, moodleoverflow, {{#str}}containsstarterrating, moodleoverflow{{/str}} {{/ pix}}{{! + !}}{{#pix}} i/status-helpful, moodleoverflow, {{#str}}containsstarterrating, moodleoverflow{{/str}} {{/pix}}{{! !}} - {{/ markedhelpful }} - {{/ questionunderreview }} + {{/markedhelpful}} + {{/questionunderreview}}
- {{# canreview }} - {{# needreview }} + {{#canreview}} + {{#needreview}} - {{/ needreview }} - {{/ canreview }} + {{/needreview}} + {{/canreview}}
{{#pix}}i/reply, mod_moodleoverflow {{/pix}}{{ replyamount }}
@@ -112,11 +112,11 @@
{{#str}} review, moodleoverflow {{/str}} {{#str}} headerstatus, moodleoverflow {{/str}} @@ -102,41 +102,41 @@ {{#discussions}}
- {{# needreview }} + {{#needreview}} {{#pix}}i/commenting, mod_moodleoverflow{{/pix}}{{ needreview }} - {{/ needreview }} + {{/needreview}} - {{# questionunderreview }} + {{#questionunderreview}} {{#pix}}i/duration, moodle, {{#str}}pending_review, mod_moodleoverflow{{/str}}{{/pix}} - {{/ questionunderreview }} - {{^ questionunderreview }} + {{/questionunderreview}} + {{^questionunderreview}} {{#markedsolution}} - {{# pix}} status/c_outline, moodleoverflow, {{#str}}teacherrating, moodleoverflow{{/str}} {{/ pix}} + {{#pix}} status/c_outline, moodleoverflow, {{#str}}teacherrating, moodleoverflow{{/str}} {{/pix}} {{/markedsolution}} {{^markedsolution}} - {{# pix}} status/c_blank, moodleoverflow, {{#str}}marknotsolved, moodleoverflow{{/str}}{{/ pix}} + {{#pix}} status/c_blank, moodleoverflow, {{#str}}marknotsolved, moodleoverflow{{/str}}{{/pix}} {{/markedsolution}} {{#markedhelpful}} - {{# pix}} status/b_outline, moodleoverflow, {{#str}}starterrating, moodleoverflow{{/str}} {{/ pix}} + {{#pix}} status/b_outline, moodleoverflow, {{#str}}starterrating, moodleoverflow{{/str}} {{/pix}} {{/markedhelpful}} {{^markedhelpful}} - {{# pix}} status/b_blank, moodleoverflow, {{#str}}marknothelpful, moodleoverflow{{/str}} {{/ pix}} + {{#pix}} status/b_blank, moodleoverflow, {{#str}}marknothelpful, moodleoverflow{{/str}} {{/pix}} {{/markedhelpful}} - {{/ questionunderreview }} + {{/questionunderreview}} - {{# userlink }}{{ username }}{{/userlink}} {{^userlink}}{{username}}{{/userlink}} + {{#userlink}}{{ username }}{{/userlink}} {{^userlink}}{{username}}{{/userlink}} - {{# lastpostusername }}{{ lastpostusername }}{{/ lastpostusername }} - {{^ lastpostusername }}-{{/ lastpostusername }}
+ {{#lastpostusername}}{{ lastpostusername }}{{/lastpostusername}} + {{^lastpostusername}}-{{/lastpostusername}}
{{ lastpostdate }}
+
- - - - diff --git a/templates/email_rejected_html.mustache b/templates/email_rejected_html.mustache index 37074e025f..c35e5b2568 100644 --- a/templates/email_rejected_html.mustache +++ b/templates/email_rejected_html.mustache @@ -78,13 +78,13 @@ } }} {{^reason}} @@ -101,36 +101,36 @@

-
+ {{{ authorpicture }}} +
{{{ subject }}}
- {{# str }} bynameondatenorating, moodleoverflow, { + {{#str}} bynameondatenorating, moodleoverflow, { "name": - {{# authorlink }}{{# quote }} {{ authorfullname }}{{/ quote }}{{/authorlink}} - {{^ authorlink }}{{# quote }} {{ authorfullname }} {{/ quote }}{{/ authorlink }} , - "date": {{# quote }}{{ postdate }}{{/ quote }} - } {{/ str }} + {{#authorlink}}{{#quote}} {{ authorfullname }}{{/quote}}{{/authorlink}} + {{^authorlink}}{{#quote}} {{ authorfullname }} {{/quote}}{{/authorlink}} , + "date": {{#quote}}{{ postdate }}{{/quote}} + } {{/str}}
- {{# grouppicture }} + + {{#grouppicture}} {{{ grouppicture }}} - {{/ grouppicture }} - {{^ grouppicture }} + {{/grouppicture}} + {{^grouppicture}}   - {{/ grouppicture }} + {{/grouppicture}} + {{{ message }}}
- {{^ firstpost }} + {{^firstpost}} - {{# str }} parent, moodleoverflow {{/ str }} + {{#str}} parent, moodleoverflow {{/str}} - {{# canreply }} + {{#canreply}} | - {{/ canreply }} - {{/ firstpost }} - {{# canreply }} + {{/canreply}} + {{/firstpost}} + {{#canreply}} - {{# str }} reply, moodleoverflow {{/ str }} + {{#str}} reply, moodleoverflow {{/str}} - {{/ canreply }} + {{/canreply}}
+
- - - -
+ {{{ authorpicture }}} +
- {{{ subject }}} + {{{subject}}}
- {{# str }} bynameondatenorating, moodleoverflow, { + {{#str}} bynameondatenorating, moodleoverflow, { "name": - {{# authorlink }}{{# quote }} {{ authorfullname }}{{/ quote }}{{/authorlink}} - {{^ authorlink }}{{# quote }} {{ authorfullname }} {{/ quote }}{{/ authorlink }} , - "date": {{# quote }}{{ postdate }}{{/ quote }} - } {{/ str }} + {{#authorlink}}{{#quote}} {{authorfullname}}{{/quote}}{{/authorlink}} + {{^authorlink}}{{#quote}} {{authorfullname}} {{/quote}}{{/authorlink}} , + "date": {{#quote}}{{postdate}}{{/quote}} + } {{/str}}
- {{# grouppicture }} - {{{ grouppicture }}} - {{/ grouppicture }} - {{^ grouppicture }} + + {{#grouppicture}} + {{{grouppicture}}} + {{/grouppicture}} + {{^grouppicture}}   - {{/ grouppicture }} + {{/grouppicture}} - {{{ message }}} + + {{{message}}}
diff --git a/templates/email_rejected_text.mustache b/templates/email_rejected_text.mustache index 138fbd51d7..072ecec4d1 100644 --- a/templates/email_rejected_text.mustache +++ b/templates/email_rejected_text.mustache @@ -77,7 +77,7 @@ "unsubscribediscussionlink": "https://example.com/mod/discussion/subscribe.php?id=2&d=2" } }} -{{{ coursename }}} -> {{{ moodleoverflowname }}}{{# showdiscussionname }} -> {{{ discussionname }}} {{/ showdiscussionname }} +{{{coursename}}} -> {{{moodleoverflowname}}}{{#showdiscussionname}} -> {{{discussionname}}} {{/showdiscussionname}} {{^reason}} {{#str}} your_post_was_rejected, mod_moodleoverflow {{/str}} @@ -91,11 +91,11 @@ ===================================================================== -{{{ subject }}} -{{# str }} bynameondate, mod_moodleoverflow, { -"name": {{# quote }}{{{ authorfullname }}}{{/ quote }}, -"date": {{# quote}}{{ postdate }}{{/ quote }} -} {{/ str }} +{{{subject}}} +{{#str}} bynameondate, mod_moodleoverflow, { +"name": {{#quote}}{{{authorfullname}}}{{/quote}}, +"date": {{#quote}}{{ postdate }}{{/quote}} +} {{/str}} --------------------------------------------------------------------- -{{{ message }}} +{{{message}}} --------------------------------------------------------------------- diff --git a/templates/email_review_needed_html.mustache b/templates/email_review_needed_html.mustache index 933339da62..215308591d 100644 --- a/templates/email_review_needed_html.mustache +++ b/templates/email_review_needed_html.mustache @@ -81,47 +81,47 @@ {{{ coursename }}} » {{{ moodleoverflowname }}} - {{# showdiscussionname }} + {{#showdiscussionname}} » {{{ discussionname }}} - {{/ showdiscussionname }} + {{/showdiscussionname}}

{{#str}}review_needed, mod_moodleoverflow{{/str}}

- +
- - - - diff --git a/templates/email_review_needed_text.mustache b/templates/email_review_needed_text.mustache index aabeeb16c4..433eac9588 100644 --- a/templates/email_review_needed_text.mustache +++ b/templates/email_review_needed_text.mustache @@ -77,15 +77,15 @@ "unsubscribediscussionlink": "https://example.com/mod/discussion/subscribe.php?id=2&d=2" } }} -{{{ coursename }}} -> {{{ moodleoverflowname }}}{{# showdiscussionname }} -> {{{ discussionname }}} {{/ showdiscussionname }} +{{{ coursename }}} -> {{{ moodleoverflowname }}}{{#showdiscussionname}} -> {{{ discussionname }}} {{/showdiscussionname}} -{{# str }}review_needed, mod_moodleoverflow {{/str}}: {{ permalink }} +{{#str}}review_needed, mod_moodleoverflow {{/str}}: {{ permalink }} {{{ subject }}} -{{# str }} bynameondate, mod_moodleoverflow, { -"name": {{# quote }}{{{ authorfullname }}}{{/ quote }}, -"date": {{# quote}}{{ postdate }}{{/ quote }} -} {{/ str }} +{{#str}} bynameondate, mod_moodleoverflow, { +"name": {{#quote}}{{{ authorfullname }}}{{/quote}}, +"date": {{#quote}}{{ postdate }}{{/quote}} +} {{/str}} --------------------------------------------------------------------- {{{ message }}} --------------------------------------------------------------------- diff --git a/templates/email_text.mustache b/templates/email_text.mustache index e869e80936..e97ec36b97 100644 --- a/templates/email_text.mustache +++ b/templates/email_text.mustache @@ -69,26 +69,26 @@ "unsubscribediscussionlink": "https://example.com/mod/discussion/subscribe.php?id=2&d=2" } }} -{{{ coursename }}} -> {{# str }} moodleoverflows, moodleoverflow {{/ str }} -> {{{ moodleoverflowname }}}{{# showdiscussionname }} -> {{{ discussionname }}} {{/ showdiscussionname }} +{{{ coursename }}} -> {{#str}} moodleoverflows, moodleoverflow {{/str}} -> {{{ moodleoverflowname }}}{{#showdiscussionname}} -> {{{ discussionname }}} {{/showdiscussionname}} {{ permalink }} {{{ subject }}} -{{# str }} bynameondate, moodleoverflow, { -"name": {{# quote }}{{{ authorfullname }}}{{/ quote }}, -"date": {{# quote}}{{ postdate }}{{/ quote }} -} {{/ str }} +{{#str}} bynameondatenorating, moodleoverflow, { +"name": {{#quote}}{{{ authorfullname }}}{{/quote}}, +"date": {{#quote}}{{ postdate }}{{/quote}} +} {{/str}} --------------------------------------------------------------------- {{{ message }}} --------------------------------------------------------------------- -{{# canreply }} - {{# str }} postmailinfolink, moodleoverflow, { - "coursename": {{# quote }}{{{ coursename }}}{{/ quote }}, - "replylink": {{# quote }}{{ replylink }}{{/ quote }} - } {{/ str }} -{{/ canreply }} -{{# unsubscribemoodleoverflowlink }} - {{# str }} unsubscribelink, moodleoverflow, {{{ unsubscribemoodleoverflowlink }}} {{/ str }} -{{/ unsubscribemoodleoverflowlink }} -{{# unsubscribediscussionlink }} - {{# str }} unsubscribediscussionlink, moodleoverflow, {{{ unsubscribediscussionlink }}} {{/ str }} -{{/ unsubscribediscussionlink }} -{{# str }} mailindexlink, moodleoverflow, {{{ moodleoverflowindexlink }}} {{/ str }} +{{#canreply}} + {{#str}} postmailinfolink, moodleoverflow, { + "coursename": {{#quote}}{{{ coursename }}}{{/quote}}, + "replylink": {{#quote}}{{ replylink }}{{/quote}} + } {{/str}} +{{/canreply}} +{{#unsubscribemoodleoverflowlink}} + {{#str}} unsubscribelink, moodleoverflow, {{{ unsubscribemoodleoverflowlink }}} {{/str}} +{{/unsubscribemoodleoverflowlink}} +{{#unsubscribediscussionlink}} + {{#str}} unsubscribediscussionlink, moodleoverflow, {{{ unsubscribediscussionlink }}} {{/str}} +{{/unsubscribediscussionlink}} +{{#str}} mailindexlink, moodleoverflow, {{{ moodleoverflowindexlink }}} {{/str}} diff --git a/templates/forum_list.mustache b/templates/forum_list.mustache index 438e383d46..8bd6109f00 100644 --- a/templates/forum_list.mustache +++ b/templates/forum_list.mustache @@ -25,7 +25,7 @@ } }} {{#hasforums}} -
+ {{{ authorpicture }}} +
{{{ subject }}}
- {{# str }} bynameondatenorating, moodleoverflow, { + {{#str}} bynameondatenorating, moodleoverflow, { "name": {{# authorlink }}{{# quote }} {{ authorfullname }}{{/ quote }}{{/authorlink}} {{^ authorlink }}{{# quote }} {{ authorfullname }} {{/ quote }}{{/ authorlink }} , "date": {{# quote }}{{ postdate }}{{/ quote }} - } {{/ str }} + } {{/str}}
- {{# grouppicture }} + + {{#grouppicture}} {{{ grouppicture }}} - {{/ grouppicture }} - {{^ grouppicture }} + {{/grouppicture}} + {{^grouppicture}}   - {{/ grouppicture }} + {{/grouppicture}} + {{{ message }}}
+
{{#currentdiscussion}} diff --git a/templates/post.mustache b/templates/post.mustache index e1002fcb54..3186b43c29 100644 --- a/templates/post.mustache +++ b/templates/post.mustache @@ -26,9 +26,9 @@ }} {{! Print an anchor if the post is the first unread post of the discussion. }} -{{# isfirstunread}} +{{#isfirstunread}} -{{/ isfirstunread}} +{{/isfirstunread}} {{! Start the post. Mark it read or unread. }}
- {{# withinreviewperiod }} + {{#withinreviewperiod}} {{> mod_moodleoverflow/review_buttons }} {{/withinreviewperiod}} - {{^ withinreviewperiod }} + {{^withinreviewperiod}} {{#str}}pending_review_but_cannot_now, mod_moodleoverflow, {{reviewdelay}} {{/str}} {{/withinreviewperiod}}
diff --git a/templates/post_dummy_cantsee.mustache b/templates/post_dummy_cantsee.mustache index 01b90f860d..63f1040606 100644 --- a/templates/post_dummy_cantsee.mustache +++ b/templates/post_dummy_cantsee.mustache @@ -27,7 +27,7 @@ -
+
{{! The heading row. }}
@@ -35,13 +35,13 @@
-
+
- {{# str }} moodleoverflowsubjecthidden, moodleoverflow {{/str}} + {{#str}} moodleoverflowsubjecthidden, moodleoverflow {{/str}}
- {{# str }} moodleoverflowauthorhidden, moodleoverflow {{/str}} + {{#str}} moodleoverflowauthorhidden, moodleoverflow {{/str}}
@@ -50,7 +50,7 @@
 
- {{# str}} moodleoverflowbodyhidden, moodleoverflow {{/str}} + {{#str}} moodleoverflowbodyhidden, moodleoverflow {{/str}}
diff --git a/tests/behat/behat_mod_moodleoverflow.php b/tests/behat/behat_mod_moodleoverflow.php index 90675b688b..9965c0e557 100644 --- a/tests/behat/behat_mod_moodleoverflow.php +++ b/tests/behat/behat_mod_moodleoverflow.php @@ -244,4 +244,22 @@ public function should_not_exist_in_the_moodleoverflow_discussion_card($element, $this->getSession() ); } + + /** + * Sets the limited answer starttime attribute of a moodleoverflow to the current time. + * + * @Given I set the :activity moodleoverflow limitedanswerstarttime to now + * @param string $activity + * @return void + */ + public function i_set_the_moodleoverflow_limitedanswerstarttime_to_now($activity): void { + global $DB; + + if (!$activityrecord = $DB->get_record('moodleoverflow', ['name' => $activity])) { + throw new Exception("Activity '$activity' not found"); + } + // Update the specified field. + $activityrecord->la_starttime = time(); + $DB->update_record('moodleoverflow', $activityrecord); + } } diff --git a/tests/behat/limitedanswer.feature b/tests/behat/limitedanswer.feature new file mode 100644 index 0000000000..73e3b343c3 --- /dev/null +++ b/tests/behat/limitedanswer.feature @@ -0,0 +1,96 @@ +@mod @mod_moodleoverflow @javascript +Feature: Moodleoverflows can start in a limited answer mode, where answers from students are not enabled until a set date. + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + + Scenario: With limited answer mode on, a teacher can answer a post that a student can not. When the teacher changes the + limitedanswer starttime to now, the student can now answer the post. + Given the following "activities" exist: + | activity | name | intro | course | idnumber | la_starttime | + | moodleoverflow | Test Moodleoverflow | Test moodleoverflow description | C1 | 1 | ##now +1 day## | + And I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Test Moodleoverflow" + And I add a new discussion to "Test Moodleoverflow" moodleoverflow with: + | Subject | Forum post 1 | + | Message | This is the question message | + And I log out + And I log in as "student1" + And I am on "Course 1" course homepage + And I follow "Test Moodleoverflow" + And I follow "Forum post 1" + And I click on "Answer" "text" + Then I should not see "Your reply" + When I set the "Test Moodleoverflow" moodleoverflow limitedanswerstarttime to now + And I am on "Course 1" course homepage + And I follow "Test Moodleoverflow" + And I follow "Forum post 1" + And I click on "Answer" "text" + Then I should see "Your reply" + And I set the following fields to these values: + | Subject | Re: Forum post 1 | + | Message | This is the answer message | + And I press "Post to forum" + Then I should see "This is the answer message" + And I should see "This is the question message" + + Scenario: Setting up the limited answer mode, the times need to be in the right order + Given the following "activities" exist: + | activity | name | intro | course | idnumber | + | moodleoverflow | Test Moodleoverflow | Test moodleoverflow description | C1 | 1 | + And I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Test Moodleoverflow" + And I navigate to "Settings" in current page administration + And I follow "Limited Answer Mode" + And I click on "la_starttime[enabled]" "checkbox" + And I set the following fields to these values: + | id_la_starttime_day | ##tomorrow##%d## | + | id_la_starttime_month | ##tomorrow##%B## | + | id_la_starttime_year | ##tomorrow##%Y## | + | id_la_starttime_hour | 12 | + | id_la_starttime_minute | 30 | + And I click on "la_endtime[enabled]" "checkbox" + And I set the following fields to these values: + | id_la_endtime_day | ##yesterday##%d## | + | id_la_endtime_month | ##yesterday##%B## | + | id_la_endtime_year | ##yesterday##%Y## | + | id_la_endtime_hour | 12 | + | id_la_endtime_minute | 30 | + When I press "Save and display" + And I follow "Limited Answer Mode" + And I click on "#collapseElement-5" "css_element" + Then I should see "End time must be in the future" + And I should see "The end time must be after the start time" + + Scenario: Setting up the limited answer mode, the start times need to be in the future + Given the following "activities" exist: + | activity | name | intro | course | idnumber | + | moodleoverflow | Test Moodleoverflow | Test moodleoverflow description | C1 | 1 | + And I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Test Moodleoverflow" + And I navigate to "Settings" in current page administration + And I follow "Limited Answer Mode" + And I click on "la_starttime[enabled]" "checkbox" + And I set the following fields to these values: + | id_la_starttime_day | ##yesterday##%d## | + | id_la_starttime_month | ##yesterday##%B## | + | id_la_starttime_year | ##yesterday##%Y## | + | id_la_starttime_hour | 12 | + | id_la_starttime_minute | 30 | + When I press "Save and display" + And I follow "Limited Answer Mode" + And I click on "#collapseElement-5" "css_element" + Then I should see "Start time must be in the future" diff --git a/tests/discussion_test.php b/tests/discussion_test.php new file mode 100644 index 0000000000..e5ee1c6c31 --- /dev/null +++ b/tests/discussion_test.php @@ -0,0 +1,164 @@ +. + +/** + * PHP Unit Tests for the Discussion class. + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_moodleoverflow; + +use mod_moodleoverflow\post\post; +use mod_moodleoverflow\discussion\discussion; + +/** + * Tests if the functions from the discussion class are working correctly. + * As the discussion class works as an administrator of the post class, most of the testcases are already realized in the + * post_test.php file. + * @package mod_moodleoverflow + * @copyright 2023 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \mod_moodleoverflow\discussion\discussion + */ +final class discussion_test extends \advanced_testcase { + + /** @var \stdClass test course */ + private $course; + + /** @var \stdClass coursemodule */ + private $coursemodule; + + /** @var \stdClass modulecontext */ + private $modulecontext; + + /** @var \stdClass test moodleoverflow */ + private $moodleoverflow; + + /** @var \stdClass test teacher */ + private $teacher; + + /** @var discussion a discussion */ + private $discussion; + + /** @var post the post from the discussion */ + private $post; + + /** @var \mod_moodleoverflow_generator $generator */ + private $generator; + + public function setUp(): void { + parent::setUp(); + $this->resetAfterTest(); + $this->helper_course_set_up(); + } + + public function tearDown(): void { + // Clear all caches. + subscriptions::reset_moodleoverflow_cache(); + subscriptions::reset_discussion_cache(); + parent::tearDown(); + } + + /** + * Test, if a discussion is being created correctly + */ + public function test_create_discussion(): void { + global $DB; + + // Build a prepost object with important information. + $time = time(); + $prepost = new \stdClass(); + $prepost->userid = $this->teacher->id; + $prepost->timenow = $time; + $prepost->message = 'a message'; + $prepost->messageformat = 1; + $prepost->reviewed = 0; + $prepost->formattachments = ''; + $prepost->modulecontext = $this->modulecontext; + + // Build a new discussion object. + $discussion = discussion::construct_without_id($this->course->id, $this->moodleoverflow->id, 'Discussion Topic', + 0, $this->teacher->id, $time, $time, $this->teacher->id); + $discussionid = $discussion->moodleoverflow_add_discussion($prepost); + $posts = $discussion->moodleoverflow_get_discussion_posts(); + $post = $posts[$discussion->get_firstpostid()]; + + // The discussion and the firstpost should be in the DB. + $dbdiscussion = $DB->get_record('moodleoverflow_discussions', ['id' => $discussion->get_id()]); + $this->assertEquals($dbdiscussion->id, $discussionid); + $this->assertEquals('Discussion Topic', $dbdiscussion->name); + + $dbpost = $DB->get_record('moodleoverflow_posts', ['id' => $discussion->get_firstpostid()]); + $this->assertEquals($dbpost->id, $post->get_id()); + $this->assertEquals($dbpost->discussion, $post->get_discussionid()); + $this->assertEquals($prepost->message, $dbpost->message); + } + + /** + * Test, if a post and its attachment are deleted successfully. + * @covers ::moodleoverflow_delete_post + */ + public function test_delete_discussion(): void { + global $DB; + // Build the prepost object with necessary information. + $prepost = new \stdClass(); + $prepost->modulecontext = $this->modulecontext; + + // Delete the discussion, but save the IDs first. + $discussionid = $this->discussion->get_id(); + $postid = $this->discussion->get_firstpostid(); + $this->discussion->moodleoverflow_delete_discussion($prepost); + + // The discussion and the post should not be in the DB anymore. + $discussion = count($DB->get_records('moodleoverflow_discussions', ['id' => $discussionid])); + $this->assertEquals(0, $discussion); + + $post = count($DB->get_records('moodleoverflow_posts', ['id' => $postid])); + $this->assertEquals(0, $post); + } + + /** + * This function creates: + * - a course with a moodleoverflow + * - a new discussion with a post. The post has an attachment. + */ + private function helper_course_set_up() { + global $DB; + // Create a new course with a moodleoverflow forum. + $this->course = $this->getDataGenerator()->create_course(); + $location = ['course' => $this->course->id]; + $this->moodleoverflow = $this->getDataGenerator()->create_module('moodleoverflow', $location); + $this->coursemodule = get_coursemodule_from_instance('moodleoverflow', $this->moodleoverflow->id); + $this->modulecontext = \context_module::instance($this->coursemodule->id); + + // Create a teacher. + $this->teacher = $this->getDataGenerator()->create_user(['firstname' => 'Tamaro', 'lastname' => 'Walter']); + $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, 'student'); + + // Create a discussion started from the teacher. + $this->generator = $this->getDataGenerator()->get_plugin_generator('mod_moodleoverflow'); + $discussion = $this->generator->post_to_forum($this->moodleoverflow, $this->teacher); + + // Get the discussion and post object. + $discussionrecord = $DB->get_record('moodleoverflow_discussions', ['id' => $discussion[0]->id]); + $postrecord = $DB->get_record('moodleoverflow_posts', ['id' => $discussion[1]->id]); + + $this->discussion = discussion::from_record($discussionrecord); + $this->post = post::from_record($postrecord); + } +} diff --git a/tests/post_test.php b/tests/post_test.php index a4727b0b3f..8b16739bbd 100644 --- a/tests/post_test.php +++ b/tests/post_test.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * PHP Unit test for post related functions in the locallib. + * PHP Unit Tests for the Post class. * * @package mod_moodleoverflow * @copyright 2023 Tamaro Walter @@ -23,43 +23,50 @@ */ namespace mod_moodleoverflow; +// Use the post class. +use context; +use mod_moodleoverflow\post\post; +use mod_moodleoverflow\discussion\discussion; +use stdClass; + defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../locallib.php'); /** - * PHP Unit test for post related functions in the locallib. + * + * Tests if the functions from the post class are working correctly. * * @package mod_moodleoverflow * @copyright 2023 Tamaro Walter * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \mod_moodleoverflow\post\post */ final class post_test extends \advanced_testcase { - /** @var \stdClass test course */ + /** @var stdClass test course */ private $course; - /** @var \stdClass coursemodule */ + /** @var stdClass coursemodule */ private $coursemodule; - /** @var \stdClass test moodleoverflow */ + /** @var stdClass modulecontext */ + private $modulecontext; + + /** @var stdClass test moodleoverflow */ private $moodleoverflow; - /** @var \stdClass test teacher */ + /** @var stdClass test teacher */ private $teacher; - /** @var \stdClass a discussion */ + /** @var discussion a discussion */ private $discussion; - /** @var \stdClass a post */ + /** @var post a post */ private $post; - /** @var \stdClass an attachment */ - private $attachment; - /** @var \mod_moodleoverflow_generator $generator */ private $generator; - public function setUp(): void { parent::setUp(); $this->resetAfterTest(); @@ -68,47 +75,79 @@ public function setUp(): void { public function tearDown(): void { // Clear all caches. - \mod_moodleoverflow\subscriptions::reset_moodleoverflow_cache(); - \mod_moodleoverflow\subscriptions::reset_discussion_cache(); + subscriptions::reset_moodleoverflow_cache(); + subscriptions::reset_discussion_cache(); parent::tearDown(); } /** - * Test if a post and its attachment are deleted successfully. - * @covers ::moodleoverflow_delete_post + * Test, if a post is being created correctly */ - public function test_moodleoverflow_delete_post(): void { + public function test_create_post(): void { + global $DB; + // Build a new post object. + $time = time(); + $message = 'a unique message'; + $post = post::construct_without_id($this->discussion->get_id(), $this->post->get_id(), $this->teacher->id, $time, + $time, $message, 0, '', 0, 1, null); + $post->moodleoverflow_add_new_post(); + + // The post should be in the database. + $postscount = count($DB->get_records('moodleoverflow_posts', ['id' => $post->get_id()])); + $this->assertEquals(1, $postscount); + } + + /** + * Test, if the message of a post can be edited successfully. + */ + public function test_edit_post(): void { global $DB; - // The attachment should exist. - $numberofattachments = count($DB->get_records('files', ['itemid' => $this->post->id])); - $this->assertEquals(2, $numberofattachments); + // The post and the attachment should exist. + $numberofattachments = count($DB->get_records('files', ['itemid' => $this->post->get_id()])); + $this->assertEquals(2, $numberofattachments); // One Attachment is saved twice in 'files'. + $post = count($DB->get_records('moodleoverflow_posts', ['id' => $this->post->get_id()])); + $this->assertEquals(1, $post); - // Delete the post from the teacher with its attachment. - moodleoverflow_delete_post($this->post, false, $this->coursemodule, $this->moodleoverflow); + // Gather important parameters. + $message = 'a new message'; - // Now try to get the attachment. - $numberofattachments = count($DB->get_records('files', ['itemid' => $this->post->id])); + $time = time(); - $this->assertEquals(0, $numberofattachments); + // Update the post. + $this->post->moodleoverflow_edit_post($time, $message, $this->post->messageformat, $this->post->formattachments); + + // The message and modified time should be changed. + $post = $DB->get_record('moodleoverflow_posts', ['id' => $this->post->get_id()]); + $this->assertEquals($message, $post->message); + $this->assertEquals($time, $post->modified); } /** - * Test if a post and its attachment are deleted successfully. - * @covers ::moodleoverflow_delete_discussion + * Test, if a post and its attachment are deleted successfully. + * @covers ::moodleoverflow_delete_post */ - public function test_moodleoverflow_delete_discussion(): void { + public function test_moodleoverflow_delete_post(): void { global $DB; - $numberofattachments = count($DB->get_records('files', ['itemid' => $this->post->id, 'filearea' => 'attachment'])); - $this->assertEquals(2, $numberofattachments); + // The post and the attachment should exist. + $numberofattachments = count($DB->get_records('files', ['itemid' => $this->post->get_id()])); + $this->assertEquals(2, $numberofattachments); // One Attachment is saved twice in 'files'. + $post = count($DB->get_records('moodleoverflow_posts', ['id' => $this->post->get_id()])); + $this->assertEquals(1, $post); - // Delete the post from the teacher with its attachment. - moodleoverflow_delete_discussion($this->discussion[0], $this->course, $this->coursemodule, $this->moodleoverflow); + // Delete the post with its attachment. + // Save the post id as it gets unsettled by the post object after being deleted. + $postid = $this->post->get_id(); + $this->post->moodleoverflow_delete_post(true); - // Now try to get the attachment. - $numberofattachments = count($DB->get_records('files', ['itemid' => $this->post->id])); + // Now try to get the attachment, it should be deleted from the database. + $numberofattachments = count($DB->get_records('files', ['itemid' => $postid])); $this->assertEquals(0, $numberofattachments); + + // Try to find the post, it should be deleted. + $post = count($DB->get_records('moodleoverflow_posts', ['id' => $postid])); + $this->assertEquals(0, $post); } /** @@ -123,6 +162,7 @@ private function helper_course_set_up() { $location = ['course' => $this->course->id]; $this->moodleoverflow = $this->getDataGenerator()->create_module('moodleoverflow', $location); $this->coursemodule = get_coursemodule_from_instance('moodleoverflow', $this->moodleoverflow->id); + $this->modulecontext = \context_module::instance($this->coursemodule->id); // Create a teacher. $this->teacher = $this->getDataGenerator()->create_user(['firstname' => 'Tamaro', 'lastname' => 'Walter']); @@ -130,29 +170,41 @@ private function helper_course_set_up() { // Create a discussion started from the teacher. $this->generator = $this->getDataGenerator()->get_plugin_generator('mod_moodleoverflow'); - $this->discussion = $this->generator->post_to_forum($this->moodleoverflow, $this->teacher); - $this->post = $DB->get_record('moodleoverflow_posts', ['id' => $this->discussion[0]->firstpost], '*'); + $discussion = $this->generator->post_to_forum($this->moodleoverflow, $this->teacher); + $discussionrecord = $DB->get_record('moodleoverflow_discussions', ['id' => $discussion[0]->id]); + $this->discussion = discussion::from_record($discussionrecord); + + // Get a temporary post from the DB to add the attachment. + $temppost = $DB->get_record('moodleoverflow_posts', ['id' => $this->discussion->get_firstpostid()]); // Create an attachment by inserting it directly in the database and update the post record. + $this->add_new_attachment($temppost, $this->modulecontext, 'world.txt', 'hello world'); - $modulecontext = \context_module::instance($this->coursemodule->id); + // Build the real post object now. That is the object that will be tested. + $postrecord = $DB->get_record('moodleoverflow_posts', ['id' => $this->discussion->get_firstpostid()]); + $this->post = post::from_record($postrecord); + } + /** + * Adds a new attachment to a post. + * + * @param stdClass $object The post object to which the attachment should be added. + * @param context $modulecontext The context of the module. + * @param string $filename The name of the file to be added. + * @param string $filecontent The content of the file to be added. + */ + private function add_new_attachment($object, $modulecontext, $filename, $filecontent) { + global $DB; $fileinfo = [ - 'contextid' => $modulecontext->id, // ID of the context. - 'component' => 'mod_moodleoverflow', // Your component name. - 'filearea' => 'attachment', // Usually = table name. - 'itemid' => $this->post->id, // Usually = ID of row in table. - 'filepath' => '/', // Any path beginning and ending in /. - 'filename' => 'NH.jpg', // Any filename. + 'contextid' => $modulecontext->id, // ID of the context. + 'component' => 'mod_moodleoverflow', // Your component name. + 'filearea' => 'attachment', // Usually = table name. + 'itemid' => $object->id, // Usually = ID of the item (e.g. the post. + 'filepath' => '/', // Any path beginning and ending in /. + 'filename' => $filename, // Any filename. ]; - $fs = get_file_storage(); - - // Create a new file containing the text 'hello world'. - $fs->create_file_from_string($fileinfo, 'hello world'); - - $this->post->attachment = 1; - $DB->update_record('moodleoverflow_posts', $this->post); - + $fs->create_file_from_string($fileinfo, $filecontent); // Creates a new file containing the text 'hello world'. + $DB->update_record('moodleoverflow_posts', $object); } } diff --git a/tests/ratings_test.php b/tests/ratings_test.php index a1f8cf30ec..5c3682ce5e 100644 --- a/tests/ratings_test.php +++ b/tests/ratings_test.php @@ -39,6 +39,26 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class ratings_test extends \advanced_testcase { + /** @var stdClass test course */ + private $course; + + /** @var stdClass coursemodule */ + private $coursemodule; + + /** @var stdClass test moodleoverflow */ + private $moodleoverflow; + + /** @var stdClass test teacher */ + private $teacher; + + /** @var stdClass test user */ + private $user1; + + /** @var stdClass another test user */ + private $user2; + + /** @var stdClass a discussion */ + private $discussion; /** @var stdClass a post from the teacher*/ private $post; @@ -61,6 +81,9 @@ final class ratings_test extends \advanced_testcase { /** @var stdClass answer from user 2 */ private $answer6; + /** @var \mod_moodleoverflow_generator $generator */ + private $generator; + /** * Test setUp. */ diff --git a/tests/subscriptions_test.php b/tests/subscriptions_test.php index 9e5a76e47e..e5a695249c 100644 --- a/tests/subscriptions_test.php +++ b/tests/subscriptions_test.php @@ -1228,7 +1228,7 @@ public function test_is_subscribable_is_guest($options): void { } /** - * Returns subscription obtions. + * Returns subscription options. * @return array */ public static function is_subscribable_loggedin_provider(): array { diff --git a/tests/userstats_test.php b/tests/userstats_test.php index 61a670129c..45a82819c9 100644 --- a/tests/userstats_test.php +++ b/tests/userstats_test.php @@ -94,8 +94,8 @@ public function setUp(): void { */ public function tearDown(): void { // Clear all caches. - \mod_moodleoverflow\subscriptions::reset_moodleoverflow_cache(); - \mod_moodleoverflow\subscriptions::reset_discussion_cache(); + subscriptions::reset_moodleoverflow_cache(); + subscriptions::reset_discussion_cache(); parent::tearDown(); } @@ -223,7 +223,7 @@ public function test_partial_anonymous(): void { } /** - * Test, if userstats are calculated correctly if the moodleoverflow is partially anonymous. + * Test, if userstats are calculated correctly if the moodleoverflow is totally anonymous. * @covers \userstats_table */ public function test_total_anonymous(): void { @@ -232,6 +232,7 @@ public function test_total_anonymous(): void { // Get the current userstats to compare later. $olduserstats = $this->create_statstable(); + $oldupvotesuser1 = $this->get_specific_userstats($olduserstats, $this->user1, 'receivedupvotes'); $oldactivityuser1 = $this->get_specific_userstats($olduserstats, $this->user1, 'forumactivity'); $oldupvotesuser2 = $this->get_specific_userstats($olduserstats, $this->user2, 'receivedupvotes'); @@ -304,7 +305,7 @@ private function helper_course_set_up() { * Makes the existing moodleoverflow anonymous. * There are 2 types of anonymous moodleoverflows: * anonymous = 1, the topic starter is anonymous - * anonymous = 2, all users are anonym + * anonymous = 2, all users are anonymous * * @param int $anonymoussetting */ diff --git a/version.php b/version.php index 2fb13655ac..6f099b71c6 100644 --- a/version.php +++ b/version.php @@ -17,19 +17,19 @@ /** * Defines the version and other meta-info about the plugin * - * Setting the $plugin->version to 0 prevents the plugin from being installed. * See https://docs.moodle.org/dev/version.php for more info. * * @package mod_moodleoverflow - * @copyright 2017 Kennet Winter + * @copyright 2025 Thomas Niedermaier, University MΓΌnster * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); +$plugin->version = 2025070700; +$plugin->requires = 2022112819; // Require Moodle 4.1. +$plugin->supported = [401, 500]; $plugin->component = 'mod_moodleoverflow'; -$plugin->version = 2025030500; -$plugin->release = 'v4.4-r3'; -$plugin->requires = 2022112800; // Requires 4.1+ Moodle version. -$plugin->maturity = MATURITY_STABLE; +$plugin->maturity = MATURITY_RC; +$plugin->release = 'v5.0-rc1'; $plugin->dependencies = [];