diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml index 3464acb76b5..08de9f5c68b 100644 --- a/.github/workflows/moodle-ci.yml +++ b/.github/workflows/moodle-ci.yml @@ -69,6 +69,7 @@ jobs: - name: PHP Copy/Paste Detector if: ${{ always() }} run: moodle-plugin-ci phpcpd + continue-on-error: true - name: PHP Mess Detector if: ${{ always() }} diff --git a/amd/build/functions.min.js b/amd/build/functions.min.js index f7e8d3db86b..8adfcc8dcb7 100644 --- a/amd/build/functions.min.js +++ b/amd/build/functions.min.js @@ -1,10 +1,10 @@ /** * Ajax functions for moodleoverflow * - * @module mod/moodleoverflow + * @module mod_moodleoverflow/functions * @copyright 2017 Tamara Gunkel * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("mod_moodleoverflow/functions",["jquery","core/ajax","core/templates","core/notification","core/config","core/url","core/str"],(function($,ajax,templates,notification,Cfg,Url,str){var t={recordvote:function(discussionid,ratingid,userid,event){var postid=$(event.target).closest(".moodleoverflowpost").prev().attr("id");postid=postid.substring(1);var vote=ajax.call([{methodname:"mod_moodleoverflow_record_vote",args:{discussionid:discussionid,postid:postid,ratingid:ratingid,sesskey:Cfg.sesskey}}]);return vote[0].done((function(response){var parentdiv=$(event.target).parent().parent();2===ratingid?(parentdiv.children("a:first-of-type").children().attr("src",Url.imageUrl("vote/upvoted","moodleoverflow")),parentdiv.children("a:nth-of-type(2)").children().attr("src",Url.imageUrl("vote/downvote","moodleoverflow"))):1===ratingid?(parentdiv.children("a:first-of-type").children().attr("src",Url.imageUrl("vote/upvote","moodleoverflow")),parentdiv.children("a:nth-of-type(2)").children().attr("src",Url.imageUrl("vote/downvoted","moodleoverflow"))):(parentdiv.children("a:first-of-type").children().attr("src",Url.imageUrl("vote/upvote","moodleoverflow")),parentdiv.children("a:nth-of-type(2)").children().attr("src",Url.imageUrl("vote/downvote","moodleoverflow"))),parentdiv.children("p").text(response.postrating),templates.replaceNode($(".user-details,.author").find('a[href*="id='+userid+'"]').siblings("span"),""+response.raterreputation+"",""),response.ownerid&&userid!==response.ownerid&&templates.replaceNode($(".user-details,.author").find('a[href*="id='+response.ownerid+'"]').siblings("span"),""+response.ownerreputation+"","")})).fail(notification.exception),vote},clickevent:function(discussionid,userid){$(".upvote").on("click",(function(event){$(event.target).is("a")&&(event.target=$(event.target).children()),$(event.target).parent().attr("class").indexOf("active")>=0?t.recordvote(discussionid,20,userid,event):t.recordvote(discussionid,2,userid,event),$(event.target).parent().toggleClass("active"),$(event.target).parent().nextAll("a").removeClass("active")})),$(".downvote").on("click",(function(event){$(event.target).is("a")&&(event.target=$(event.target).children()),$(event.target).parent().attr("class").indexOf("active")>=0?t.recordvote(discussionid,10,userid,event):t.recordvote(discussionid,1,userid,event),$(event.target).parent().toggleClass("active"),$(event.target).parent().prevAll("a").removeClass("active")})),$(".marksolved").on("click",(function(event){var post=$(event.target).parents(".moodleoverflowpost");post.hasClass("statusteacher")||post.hasClass("statusboth")?t.recordvote(discussionid,30,userid,event)[0].then((function(){t.removeSolvedFromPost(post)})):t.recordvote(discussionid,3,userid,event)[0].then((function(){t.removeOtherSolved(post.parent().parent()),post.hasClass("statusstarter")?(post.removeClass("statusstarter"),post.addClass("statusboth")):post.addClass("statusteacher");var promiseStringNotSolved=str.get_string("marknotsolved","mod_moodleoverflow");$.when(promiseStringNotSolved).done((function(string){$(event.target).text(string)})),t.redoStatus(post)}))})),$(".markhelpful").on("click",(function(event){var post=$(event.target).parents(".moodleoverflowpost");post.hasClass("statusstarter")||post.hasClass("statusboth")?t.recordvote(discussionid,40,userid,event)[0].then((function(){t.removeHelpfulFromPost(post)})):t.recordvote(discussionid,4,userid,event)[0].then((function(){t.removeOtherHelpful(post.parent().parent()),post.hasClass("statusteacher")?(post.removeClass("statusteacher"),post.addClass("statusboth")):post.addClass("statusstarter");var promiseStringNotHelpful=str.get_string("marknothelpful","mod_moodleoverflow");$.when(promiseStringNotHelpful).done((function(string){$(event.target).text(string)})),t.redoStatus(post)}))}))},removeHelpfulFromPost:function(post){post.hasClass("statusstarter")?post.removeClass("statusstarter"):(post.removeClass("statusboth"),post.addClass("statusteacher")),t.redoStatus(post);var promiseHelpful=str.get_string("markhelpful","mod_moodleoverflow");$.when(promiseHelpful).done((function(string){post.find(".markhelpful").text(string)}))},removeOtherHelpful:function(root){var formerhelpful=root.find(".statusstarter, .statusboth");formerhelpful.length>0&&t.removeHelpfulFromPost(formerhelpful)},removeSolvedFromPost:function(post){post.hasClass("statusteacher")?post.removeClass("statusteacher"):(post.removeClass("statusboth"),post.addClass("statusstarter")),t.redoStatus(post);var promiseHelpful=str.get_string("marksolved","mod_moodleoverflow");$.when(promiseHelpful).done((function(string){post.find(".marksolved").text(string)}))},removeOtherSolved:function(root){var formersolution=root.find(".statusteacher, .statusboth");formersolution.length>0&&t.removeSolvedFromPost(formersolution)},redoStatus:function(post){if($(post).hasClass("statusboth")){str.get_strings([{key:"teacherrating",component:"mod_moodleoverflow"},{key:"starterrating",component:"mod_moodleoverflow"},{key:"bestanswer",component:"mod_moodleoverflow"}]).then((function(results){var circle=templates.renderPix("status/c_circle","mod_moodleoverflow",results[0]),box=templates.renderPix("status/b_box","mod_moodleoverflow",results[1]);return $.when(box,circle).done((function(boxImg,circleImg){screen.width>600?post.find(".status").html(boxImg+circleImg+results[2]):post.find(".status").html(boxImg+circleImg)})),results}))}else if($(post).hasClass("statusteacher")){str.get_strings([{key:"teacherrating",component:"mod_moodleoverflow"},{key:"solvedanswer",component:"mod_moodleoverflow"}]).then((function(results){var circle=templates.renderPix("status/c_outline","mod_moodleoverflow",results[0]);return $.when(circle).done((function(circleImg){screen.width>600?post.find(".status").html(circleImg+results[1]):post.find(".status").html(circleImg)})),results}))}else if($(post).hasClass("statusstarter")){str.get_strings([{key:"starterrating",component:"mod_moodleoverflow"},{key:"helpfulanswer",component:"mod_moodleoverflow"}]).then((function(results){var box=templates.renderPix("status/b_outline","mod_moodleoverflow",results[0]);return $.when(box).done((function(boxImg){screen.width>600?post.find(".status").html(boxImg+results[1]):post.find(".status").html(boxImg)})),results}))}else post.find(".status").html("")}};return t})); +define("mod_moodleoverflow/functions",["jquery","core/ajax","core/templates","core/notification","core/config","core/url","core/str"],(function($,ajax,templates,notification,Cfg,Url,str){var t={recordvote:function(discussionid,ratingid,userid,event){var postid=$(event.target).closest(".moodleoverflowpost").attr("id");postid=postid.substring(1);var vote=ajax.call([{methodname:"mod_moodleoverflow_record_vote",args:{discussionid:discussionid,postid:postid,ratingid:ratingid,sesskey:Cfg.sesskey}}]);return vote[0].done((function(response){var parentdiv=$(event.target).parent().parent();2===ratingid?(parentdiv.children("a:first-of-type").children().attr("src",Url.imageUrl("vote/upvoted","moodleoverflow")),parentdiv.children("a:nth-of-type(2)").children().attr("src",Url.imageUrl("vote/downvote","moodleoverflow"))):1===ratingid?(parentdiv.children("a:first-of-type").children().attr("src",Url.imageUrl("vote/upvote","moodleoverflow")),parentdiv.children("a:nth-of-type(2)").children().attr("src",Url.imageUrl("vote/downvoted","moodleoverflow"))):(parentdiv.children("a:first-of-type").children().attr("src",Url.imageUrl("vote/upvote","moodleoverflow")),parentdiv.children("a:nth-of-type(2)").children().attr("src",Url.imageUrl("vote/downvote","moodleoverflow"))),parentdiv.children("p").text(response.postrating),templates.replaceNode($(".user-details,.author").find('a[href*="id='+userid+'"]').siblings("span"),""+response.raterreputation+"",""),response.ownerid&&userid!==response.ownerid&&templates.replaceNode($(".user-details,.author").find('a[href*="id='+response.ownerid+'"]').siblings("span"),""+response.ownerreputation+"","")})).fail(notification.exception),vote},clickevent:function(discussionid,userid){$(".upvote").on("click",(function(event){$(event.target).is("a")&&(event.target=$(event.target).children()),$(event.target).parent().attr("class").indexOf("active")>=0?t.recordvote(discussionid,20,userid,event):t.recordvote(discussionid,2,userid,event),$(event.target).parent().toggleClass("active"),$(event.target).parent().nextAll("a").removeClass("active")})),$(".downvote").on("click",(function(event){$(event.target).is("a")&&(event.target=$(event.target).children()),$(event.target).parent().attr("class").indexOf("active")>=0?t.recordvote(discussionid,10,userid,event):t.recordvote(discussionid,1,userid,event),$(event.target).parent().toggleClass("active"),$(event.target).parent().prevAll("a").removeClass("active")})),$(".marksolved").on("click",(function(event){var post=$(event.target).parents(".moodleoverflowpost");post.hasClass("statusteacher")||post.hasClass("statusboth")?t.recordvote(discussionid,30,userid,event)[0].then((function(){t.removeSolvedFromPost(post)})):t.recordvote(discussionid,3,userid,event)[0].then((function(){t.removeOtherSolved(post.parent().parent()),post.hasClass("statusstarter")?(post.removeClass("statusstarter"),post.addClass("statusboth")):post.addClass("statusteacher");var promiseStringNotSolved=str.get_string("marknotsolved","mod_moodleoverflow");$.when(promiseStringNotSolved).done((function(string){$(event.target).text(string)})),t.redoStatus(post)}))})),$(".markhelpful").on("click",(function(event){var post=$(event.target).parents(".moodleoverflowpost");post.hasClass("statusstarter")||post.hasClass("statusboth")?t.recordvote(discussionid,40,userid,event)[0].then((function(){t.removeHelpfulFromPost(post)})):t.recordvote(discussionid,4,userid,event)[0].then((function(){t.removeOtherHelpful(post.parent().parent()),post.hasClass("statusteacher")?(post.removeClass("statusteacher"),post.addClass("statusboth")):post.addClass("statusstarter");var promiseStringNotHelpful=str.get_string("marknothelpful","mod_moodleoverflow");$.when(promiseStringNotHelpful).done((function(string){$(event.target).text(string)})),t.redoStatus(post)}))}))},removeHelpfulFromPost:function(post){post.hasClass("statusstarter")?post.removeClass("statusstarter"):(post.removeClass("statusboth"),post.addClass("statusteacher")),t.redoStatus(post);var promiseHelpful=str.get_string("markhelpful","mod_moodleoverflow");$.when(promiseHelpful).done((function(string){post.find(".markhelpful").text(string)}))},removeOtherHelpful:function(root){var formerhelpful=root.find(".statusstarter, .statusboth");formerhelpful.length>0&&t.removeHelpfulFromPost(formerhelpful)},removeSolvedFromPost:function(post){post.hasClass("statusteacher")?post.removeClass("statusteacher"):(post.removeClass("statusboth"),post.addClass("statusstarter")),t.redoStatus(post);var promiseHelpful=str.get_string("marksolved","mod_moodleoverflow");$.when(promiseHelpful).done((function(string){post.find(".marksolved").text(string)}))},removeOtherSolved:function(root){var formersolution=root.find(".statusteacher, .statusboth");formersolution.length>0&&t.removeSolvedFromPost(formersolution)},redoStatus:function(post){if($(post).hasClass("statusboth")){str.get_strings([{key:"teacherrating",component:"mod_moodleoverflow"},{key:"starterrating",component:"mod_moodleoverflow"},{key:"bestanswer",component:"mod_moodleoverflow"}]).then((function(results){var circle=templates.renderPix("status/c_circle","mod_moodleoverflow",results[0]),box=templates.renderPix("status/b_box","mod_moodleoverflow",results[1]);return $.when(box,circle).done((function(boxImg,circleImg){screen.width>600?post.find(".status").html(boxImg+circleImg+results[2]):post.find(".status").html(boxImg+circleImg)})),results}))}else if($(post).hasClass("statusteacher")){str.get_strings([{key:"teacherrating",component:"mod_moodleoverflow"},{key:"solvedanswer",component:"mod_moodleoverflow"}]).then((function(results){var circle=templates.renderPix("status/c_outline","mod_moodleoverflow",results[0]);return $.when(circle).done((function(circleImg){screen.width>600?post.find(".status").html(circleImg+results[1]):post.find(".status").html(circleImg)})),results}))}else if($(post).hasClass("statusstarter")){str.get_strings([{key:"starterrating",component:"mod_moodleoverflow"},{key:"helpfulanswer",component:"mod_moodleoverflow"}]).then((function(results){var box=templates.renderPix("status/b_outline","mod_moodleoverflow",results[0]);return $.when(box).done((function(boxImg){screen.width>600?post.find(".status").html(boxImg+results[1]):post.find(".status").html(boxImg)})),results}))}else post.find(".status").html("")}};return t})); //# sourceMappingURL=functions.min.js.map \ No newline at end of file diff --git a/amd/build/functions.min.js.map b/amd/build/functions.min.js.map index bdaff9eb1fa..8edc3539b36 100644 --- a/amd/build/functions.min.js.map +++ b/amd/build/functions.min.js.map @@ -1 +1 @@ -{"version":3,"file":"functions.min.js","sources":["../src/functions.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 * Ajax functions for moodleoverflow\n *\n * @module mod/moodleoverflow\n * @copyright 2017 Tamara Gunkel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/config', 'core/url', 'core/str'],\n function($, ajax, templates, notification, Cfg, Url, str) {\n\n var RATING_SOLVED = 3;\n var RATING_REMOVE_SOLVED = 30;\n var RATING_HELPFUL = 4;\n var RATING_REMOVE_HELPFUL = 40;\n\n var t = {\n\n /**\n * Reoords a upvote / downvote.\n * @param {int} discussionid\n * @param {int} ratingid\n * @param {int} userid\n * @param {event} event\n * @returns {string}\n */\n recordvote: function(discussionid, ratingid, userid, event) {\n var target = $(event.target).closest('.moodleoverflowpost').prev();\n var postid = target.attr('id');\n postid = postid.substring(1);\n\n var vote = ajax.call([{\n methodname: 'mod_moodleoverflow_record_vote',\n args: {\n discussionid: discussionid,\n postid: postid,\n ratingid: ratingid,\n sesskey: Cfg.sesskey\n }\n }\n ]);\n\n vote[0].done(function(response) {\n\n var parentdiv = $(event.target).parent().parent();\n // Update Votes.\n if (ratingid === 2) {\n parentdiv.children('a:first-of-type').children().attr(\n 'src', Url.imageUrl('vote/upvoted', 'moodleoverflow'));\n parentdiv.children('a:nth-of-type(2)').children().attr(\n 'src', Url.imageUrl('vote/downvote', 'moodleoverflow'));\n } else if (ratingid === 1) {\n parentdiv.children('a:first-of-type').children().attr(\n 'src', Url.imageUrl('vote/upvote', 'moodleoverflow'));\n parentdiv.children('a:nth-of-type(2)').children().attr(\n 'src', Url.imageUrl('vote/downvoted', 'moodleoverflow'));\n } else {\n parentdiv.children('a:first-of-type').children().attr(\n 'src', Url.imageUrl('vote/upvote', 'moodleoverflow'));\n parentdiv.children('a:nth-of-type(2)').children().attr(\n 'src', Url.imageUrl('vote/downvote', 'moodleoverflow'));\n }\n\n parentdiv.children('p').text(response.postrating);\n\n // Update user reputation.\n templates.replaceNode($('.user-details,.author').find('a[href*=\"id=' + userid + '\"]')\n .siblings('span'), '' + response.raterreputation + '', \"\");\n if (response.ownerid && userid !== response.ownerid) {\n templates.replaceNode($('.user-details,.author').find('a[href*=\"id=' + response.ownerid + '\"]')\n .siblings('span'), '' + response.ownerreputation + '', \"\");\n }\n }).fail(notification.exception);\n\n return vote;\n },\n\n /**\n * Initializes the clickevent on upvotes / downvotes.\n * @param {int} discussionid\n * @param {int} userid\n */\n clickevent: function(discussionid, userid) {\n $(\".upvote\").on(\"click\", function(event) {\n if ($(event.target).is('a')) {\n event.target = $(event.target).children();\n }\n\n if ($(event.target).parent().attr('class').indexOf('active') >= 0) {\n t.recordvote(discussionid, 20, userid, event);\n } else {\n t.recordvote(discussionid, 2, userid, event);\n }\n $(event.target).parent().toggleClass('active');\n $(event.target).parent().nextAll('a').removeClass('active');\n });\n\n $(\".downvote\").on(\"click\", function(event) {\n if ($(event.target).is('a')) {\n event.target = $(event.target).children();\n }\n\n if ($(event.target).parent().attr('class').indexOf('active') >= 0) {\n t.recordvote(discussionid, 10, userid, event);\n } else {\n t.recordvote(discussionid, 1, userid, event);\n }\n $(event.target).parent().toggleClass('active');\n $(event.target).parent().prevAll('a').removeClass('active');\n });\n\n $(\".marksolved\").on(\"click\", function(event) {\n var post = $(event.target).parents('.moodleoverflowpost');\n\n if (post.hasClass('statusteacher') || post.hasClass('statusboth')) {\n // Remove solution mark.\n t.recordvote(discussionid, RATING_REMOVE_SOLVED, userid, event)[0].then(function() {\n t.removeSolvedFromPost(post);\n });\n } else {\n // Add solution mark.\n t.recordvote(discussionid, RATING_SOLVED, userid, event)[0].then(function() {\n // Remove other solution mark in dom.\n t.removeOtherSolved(post.parent().parent());\n if (post.hasClass('statusstarter')) {\n post.removeClass('statusstarter');\n post.addClass('statusboth');\n } else {\n post.addClass('statusteacher');\n }\n\n var promiseStringNotSolved = str.get_string('marknotsolved', 'mod_moodleoverflow');\n $.when(promiseStringNotSolved).done(function(string) {\n $(event.target).text(string);\n });\n t.redoStatus(post);\n });\n }\n\n\n });\n\n $(\".markhelpful\").on(\"click\", function(event) {\n var post = $(event.target).parents('.moodleoverflowpost');\n\n if (post.hasClass('statusstarter') || post.hasClass('statusboth')) {\n // Remove helpful mark.\n t.recordvote(discussionid, RATING_REMOVE_HELPFUL, userid, event)[0].then(function() {\n t.removeHelpfulFromPost(post);\n });\n } else {\n // Add helpful mark.\n t.recordvote(discussionid, RATING_HELPFUL, userid, event)[0].then(function() {\n // Remove other helpful mark in dom.\n t.removeOtherHelpful(post.parent().parent());\n if (post.hasClass('statusteacher')) {\n post.removeClass('statusteacher');\n post.addClass('statusboth');\n } else {\n post.addClass('statusstarter');\n }\n\n var promiseStringNotHelpful = str.get_string('marknothelpful', 'mod_moodleoverflow');\n $.when(promiseStringNotHelpful).done(function(string) {\n $(event.target).text(string);\n });\n t.redoStatus(post);\n });\n }\n\n });\n },\n\n removeHelpfulFromPost: function (post) {\n if (post.hasClass('statusstarter')) {\n post.removeClass('statusstarter');\n } else {\n post.removeClass('statusboth');\n post.addClass('statusteacher');\n }\n\n t.redoStatus(post);\n\n var promiseHelpful = str.get_string('markhelpful', 'mod_moodleoverflow');\n $.when(promiseHelpful).done(function (string) {\n post.find('.markhelpful').text(string);\n });\n },\n\n removeOtherHelpful: function(root) {\n var formerhelpful = root.find('.statusstarter, .statusboth');\n if (formerhelpful.length > 0) {\n t.removeHelpfulFromPost(formerhelpful);\n }\n },\n\n removeSolvedFromPost: function(post) {\n if (post.hasClass('statusteacher')) {\n post.removeClass('statusteacher');\n } else {\n post.removeClass('statusboth');\n post.addClass('statusstarter');\n }\n\n t.redoStatus(post);\n\n var promiseHelpful = str.get_string('marksolved', 'mod_moodleoverflow');\n $.when(promiseHelpful).done(function(string) {\n post.find('.marksolved').text(string);\n });\n },\n\n removeOtherSolved: function(root) {\n var formersolution = root.find('.statusteacher, .statusboth');\n if (formersolution.length > 0) {\n t.removeSolvedFromPost(formersolution);\n }\n },\n\n /**\n * Redoes the post status\n * @param {object} post dom with .moodleoverflowpost which status should be redone\n */\n redoStatus: function(post) {\n if ($(post).hasClass('statusboth')) {\n var statusBothRequest = [\n {key: 'teacherrating', component: 'mod_moodleoverflow'},\n {key: 'starterrating', component: 'mod_moodleoverflow'},\n {key: 'bestanswer', component: 'mod_moodleoverflow'}\n ];\n str.get_strings(statusBothRequest).then(function(results) {\n var circle = templates.renderPix('status/c_circle', 'mod_moodleoverflow', results[0]);\n var box = templates.renderPix('status/b_box', 'mod_moodleoverflow', results[1]);\n $.when(box, circle).done(function(boxImg, circleImg) {\n if (screen.width > 600) {\n post.find('.status').html(boxImg + circleImg + results[2]);\n } else {\n post.find('.status').html(boxImg + circleImg);\n }\n });\n return results;\n });\n } else if ($(post).hasClass('statusteacher')) {\n var statusTeacherRequest = [\n {key: 'teacherrating', component: 'mod_moodleoverflow'},\n {key: 'solvedanswer', component: 'mod_moodleoverflow'}\n ];\n str.get_strings(statusTeacherRequest).then(function(results) {\n var circle = templates.renderPix('status/c_outline', 'mod_moodleoverflow', results[0]);\n $.when(circle).done(function(circleImg) {\n if (screen.width > 600) {\n post.find('.status').html(circleImg + results[1]);\n } else {\n post.find('.status').html(circleImg);\n }\n\n });\n return results;\n });\n } else if ($(post).hasClass('statusstarter')) {\n var statusStarterRequest = [\n {key: 'starterrating', component: 'mod_moodleoverflow'},\n {key: 'helpfulanswer', component: 'mod_moodleoverflow'}\n ];\n str.get_strings(statusStarterRequest).then(function(results) {\n var box = templates.renderPix('status/b_outline', 'mod_moodleoverflow', results[0]);\n $.when(box).done(function(boxImg) {\n if (screen.width > 600) {\n post.find('.status').html(boxImg + results[1]);\n } else {\n post.find('.status').html(boxImg);\n }\n });\n return results;\n });\n } else {\n post.find('.status').html('');\n }\n\n }\n };\n\n return t;\n});\n"],"names":["define","$","ajax","templates","notification","Cfg","Url","str","t","recordvote","discussionid","ratingid","userid","event","postid","target","closest","prev","attr","substring","vote","call","methodname","args","sesskey","done","response","parentdiv","parent","children","imageUrl","text","postrating","replaceNode","find","siblings","raterreputation","ownerid","ownerreputation","fail","exception","clickevent","on","is","indexOf","toggleClass","nextAll","removeClass","prevAll","post","parents","hasClass","then","removeSolvedFromPost","removeOtherSolved","addClass","promiseStringNotSolved","get_string","when","string","redoStatus","removeHelpfulFromPost","removeOtherHelpful","promiseStringNotHelpful","promiseHelpful","root","formerhelpful","length","formersolution","get_strings","key","component","results","circle","renderPix","box","boxImg","circleImg","screen","width","html"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"functions.min.js","sources":["../src/functions.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 * Ajax functions for moodleoverflow\n *\n * @module mod_moodleoverflow/functions\n * @copyright 2017 Tamara Gunkel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/config', 'core/url', 'core/str'],\n function($, ajax, templates, notification, Cfg, Url, str) {\n\n var RATING_SOLVED = 3;\n var RATING_REMOVE_SOLVED = 30;\n var RATING_HELPFUL = 4;\n var RATING_REMOVE_HELPFUL = 40;\n\n var t = {\n\n /**\n * Reoords a upvote / downvote.\n * @param {int} discussionid\n * @param {int} ratingid\n * @param {int} userid\n * @param {event} event\n * @returns {string}\n */\n recordvote: function(discussionid, ratingid, userid, event) {\n var target = $(event.target).closest('.moodleoverflowpost');\n var postid = target.attr('id');\n postid = postid.substring(1);\n\n var vote = ajax.call([{\n methodname: 'mod_moodleoverflow_record_vote',\n args: {\n discussionid: discussionid,\n postid: postid,\n ratingid: ratingid,\n sesskey: Cfg.sesskey\n }\n }\n ]);\n\n vote[0].done(function(response) {\n\n var parentdiv = $(event.target).parent().parent();\n // Update Votes.\n if (ratingid === 2) {\n parentdiv.children('a:first-of-type').children().attr(\n 'src', Url.imageUrl('vote/upvoted', 'moodleoverflow'));\n parentdiv.children('a:nth-of-type(2)').children().attr(\n 'src', Url.imageUrl('vote/downvote', 'moodleoverflow'));\n } else if (ratingid === 1) {\n parentdiv.children('a:first-of-type').children().attr(\n 'src', Url.imageUrl('vote/upvote', 'moodleoverflow'));\n parentdiv.children('a:nth-of-type(2)').children().attr(\n 'src', Url.imageUrl('vote/downvoted', 'moodleoverflow'));\n } else {\n parentdiv.children('a:first-of-type').children().attr(\n 'src', Url.imageUrl('vote/upvote', 'moodleoverflow'));\n parentdiv.children('a:nth-of-type(2)').children().attr(\n 'src', Url.imageUrl('vote/downvote', 'moodleoverflow'));\n }\n\n parentdiv.children('p').text(response.postrating);\n\n // Update user reputation.\n templates.replaceNode($('.user-details,.author').find('a[href*=\"id=' + userid + '\"]')\n .siblings('span'), '' + response.raterreputation + '', \"\");\n if (response.ownerid && userid !== response.ownerid) {\n templates.replaceNode($('.user-details,.author').find('a[href*=\"id=' + response.ownerid + '\"]')\n .siblings('span'), '' + response.ownerreputation + '', \"\");\n }\n }).fail(notification.exception);\n\n return vote;\n },\n\n /**\n * Initializes the clickevent on upvotes / downvotes.\n * @param {int} discussionid\n * @param {int} userid\n */\n clickevent: function(discussionid, userid) {\n $(\".upvote\").on(\"click\", function(event) {\n if ($(event.target).is('a')) {\n event.target = $(event.target).children();\n }\n\n if ($(event.target).parent().attr('class').indexOf('active') >= 0) {\n t.recordvote(discussionid, 20, userid, event);\n } else {\n t.recordvote(discussionid, 2, userid, event);\n }\n $(event.target).parent().toggleClass('active');\n $(event.target).parent().nextAll('a').removeClass('active');\n });\n\n $(\".downvote\").on(\"click\", function(event) {\n if ($(event.target).is('a')) {\n event.target = $(event.target).children();\n }\n\n if ($(event.target).parent().attr('class').indexOf('active') >= 0) {\n t.recordvote(discussionid, 10, userid, event);\n } else {\n t.recordvote(discussionid, 1, userid, event);\n }\n $(event.target).parent().toggleClass('active');\n $(event.target).parent().prevAll('a').removeClass('active');\n });\n\n $(\".marksolved\").on(\"click\", function(event) {\n var post = $(event.target).parents('.moodleoverflowpost');\n\n if (post.hasClass('statusteacher') || post.hasClass('statusboth')) {\n // Remove solution mark.\n t.recordvote(discussionid, RATING_REMOVE_SOLVED, userid, event)[0].then(function() {\n t.removeSolvedFromPost(post);\n });\n } else {\n // Add solution mark.\n t.recordvote(discussionid, RATING_SOLVED, userid, event)[0].then(function() {\n // Remove other solution mark in dom.\n t.removeOtherSolved(post.parent().parent());\n if (post.hasClass('statusstarter')) {\n post.removeClass('statusstarter');\n post.addClass('statusboth');\n } else {\n post.addClass('statusteacher');\n }\n\n var promiseStringNotSolved = str.get_string('marknotsolved', 'mod_moodleoverflow');\n $.when(promiseStringNotSolved).done(function(string) {\n $(event.target).text(string);\n });\n t.redoStatus(post);\n });\n }\n\n\n });\n\n $(\".markhelpful\").on(\"click\", function(event) {\n var post = $(event.target).parents('.moodleoverflowpost');\n\n if (post.hasClass('statusstarter') || post.hasClass('statusboth')) {\n // Remove helpful mark.\n t.recordvote(discussionid, RATING_REMOVE_HELPFUL, userid, event)[0].then(function() {\n t.removeHelpfulFromPost(post);\n });\n } else {\n // Add helpful mark.\n t.recordvote(discussionid, RATING_HELPFUL, userid, event)[0].then(function() {\n // Remove other helpful mark in dom.\n t.removeOtherHelpful(post.parent().parent());\n if (post.hasClass('statusteacher')) {\n post.removeClass('statusteacher');\n post.addClass('statusboth');\n } else {\n post.addClass('statusstarter');\n }\n\n var promiseStringNotHelpful = str.get_string('marknothelpful', 'mod_moodleoverflow');\n $.when(promiseStringNotHelpful).done(function(string) {\n $(event.target).text(string);\n });\n t.redoStatus(post);\n });\n }\n\n });\n },\n\n removeHelpfulFromPost: function (post) {\n if (post.hasClass('statusstarter')) {\n post.removeClass('statusstarter');\n } else {\n post.removeClass('statusboth');\n post.addClass('statusteacher');\n }\n\n t.redoStatus(post);\n\n var promiseHelpful = str.get_string('markhelpful', 'mod_moodleoverflow');\n $.when(promiseHelpful).done(function (string) {\n post.find('.markhelpful').text(string);\n });\n },\n\n removeOtherHelpful: function(root) {\n var formerhelpful = root.find('.statusstarter, .statusboth');\n if (formerhelpful.length > 0) {\n t.removeHelpfulFromPost(formerhelpful);\n }\n },\n\n removeSolvedFromPost: function(post) {\n if (post.hasClass('statusteacher')) {\n post.removeClass('statusteacher');\n } else {\n post.removeClass('statusboth');\n post.addClass('statusstarter');\n }\n\n t.redoStatus(post);\n\n var promiseHelpful = str.get_string('marksolved', 'mod_moodleoverflow');\n $.when(promiseHelpful).done(function(string) {\n post.find('.marksolved').text(string);\n });\n },\n\n removeOtherSolved: function(root) {\n var formersolution = root.find('.statusteacher, .statusboth');\n if (formersolution.length > 0) {\n t.removeSolvedFromPost(formersolution);\n }\n },\n\n /**\n * Redoes the post status\n * @param {object} post dom with .moodleoverflowpost which status should be redone\n */\n redoStatus: function(post) {\n if ($(post).hasClass('statusboth')) {\n var statusBothRequest = [\n {key: 'teacherrating', component: 'mod_moodleoverflow'},\n {key: 'starterrating', component: 'mod_moodleoverflow'},\n {key: 'bestanswer', component: 'mod_moodleoverflow'}\n ];\n str.get_strings(statusBothRequest).then(function(results) {\n var circle = templates.renderPix('status/c_circle', 'mod_moodleoverflow', results[0]);\n var box = templates.renderPix('status/b_box', 'mod_moodleoverflow', results[1]);\n $.when(box, circle).done(function(boxImg, circleImg) {\n if (screen.width > 600) {\n post.find('.status').html(boxImg + circleImg + results[2]);\n } else {\n post.find('.status').html(boxImg + circleImg);\n }\n });\n return results;\n });\n } else if ($(post).hasClass('statusteacher')) {\n var statusTeacherRequest = [\n {key: 'teacherrating', component: 'mod_moodleoverflow'},\n {key: 'solvedanswer', component: 'mod_moodleoverflow'}\n ];\n str.get_strings(statusTeacherRequest).then(function(results) {\n var circle = templates.renderPix('status/c_outline', 'mod_moodleoverflow', results[0]);\n $.when(circle).done(function(circleImg) {\n if (screen.width > 600) {\n post.find('.status').html(circleImg + results[1]);\n } else {\n post.find('.status').html(circleImg);\n }\n\n });\n return results;\n });\n } else if ($(post).hasClass('statusstarter')) {\n var statusStarterRequest = [\n {key: 'starterrating', component: 'mod_moodleoverflow'},\n {key: 'helpfulanswer', component: 'mod_moodleoverflow'}\n ];\n str.get_strings(statusStarterRequest).then(function(results) {\n var box = templates.renderPix('status/b_outline', 'mod_moodleoverflow', results[0]);\n $.when(box).done(function(boxImg) {\n if (screen.width > 600) {\n post.find('.status').html(boxImg + results[1]);\n } else {\n post.find('.status').html(boxImg);\n }\n });\n return results;\n });\n } else {\n post.find('.status').html('');\n }\n\n }\n };\n\n return t;\n});\n"],"names":["define","$","ajax","templates","notification","Cfg","Url","str","t","recordvote","discussionid","ratingid","userid","event","postid","target","closest","attr","substring","vote","call","methodname","args","sesskey","done","response","parentdiv","parent","children","imageUrl","text","postrating","replaceNode","find","siblings","raterreputation","ownerid","ownerreputation","fail","exception","clickevent","on","is","indexOf","toggleClass","nextAll","removeClass","prevAll","post","parents","hasClass","then","removeSolvedFromPost","removeOtherSolved","addClass","promiseStringNotSolved","get_string","when","string","redoStatus","removeHelpfulFromPost","removeOtherHelpful","promiseStringNotHelpful","promiseHelpful","root","formerhelpful","length","formersolution","get_strings","key","component","results","circle","renderPix","box","boxImg","circleImg","screen","width","html"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/reviewing.min.js b/amd/build/reviewing.min.js new file mode 100644 index 00000000000..7dc1ac1e4fa --- /dev/null +++ b/amd/build/reviewing.min.js @@ -0,0 +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}} +/** + * 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)})); + +//# 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 new file mode 100644 index 00000000000..d5002e736d7 --- /dev/null +++ b/amd/build/reviewing.min.js.map @@ -0,0 +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 diff --git a/amd/src/functions.js b/amd/src/functions.js index 90b80fe9bb6..21a7f8ead57 100644 --- a/amd/src/functions.js +++ b/amd/src/functions.js @@ -16,7 +16,7 @@ /** * Ajax functions for moodleoverflow * - * @module mod/moodleoverflow + * @module mod_moodleoverflow/functions * @copyright 2017 Tamara Gunkel * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -39,7 +39,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/conf * @returns {string} */ recordvote: function(discussionid, ratingid, userid, event) { - var target = $(event.target).closest('.moodleoverflowpost').prev(); + var target = $(event.target).closest('.moodleoverflowpost'); var postid = target.attr('id'); postid = postid.substring(1); diff --git a/amd/src/reviewing.js b/amd/src/reviewing.js new file mode 100644 index 00000000000..0525609f597 --- /dev/null +++ b/amd/src/reviewing.js @@ -0,0 +1,96 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * 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 + */ +import Ajax from 'core/ajax'; +import Prefetch from 'core/prefetch'; +import Templates from 'core/templates'; +import {get_string as getString} from 'core/str'; + +/** + * Init function. + */ +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']); + + const root = document.getElementById('moodleoverflow-posts'); + root.onclick = async(e) => { + const action = e.target.getAttribute('data-moodleoverflow-action'); + + if (!action) { + return; + } + + const post = e.target.closest('*[data-moodleoverflow-postid]'); + const reviewRow = e.target.closest('.reviewrow'); + const postID = post.getAttribute('data-moodleoverflow-postid'); + + if (action === 'approve') { + reviewRow.innerHTML = '.'; + const nextPostURL = await Ajax.call([{ + methodname: 'mod_moodleoverflow_review_approve_post', + args: { + postid: postID, + } + }])[0]; + + let message = await getString('post_was_approved', 'mod_moodleoverflow') + ' '; + if (nextPostURL) { + message += `` + + await getString('jump_to_next_post_needing_review', 'mod_moodleoverflow') + + ""; + } else { + message += await getString('there_are_no_posts_needing_review', 'mod_moodleoverflow'); + } + reviewRow.innerHTML = message; + post.classList.remove("pendingreview"); + } else if (action === 'reject') { + reviewRow.innerHTML = '.'; + reviewRow.innerHTML = await Templates.render('mod_moodleoverflow/reject_post_form', {}); + } else if (action === 'reject-submit') { + const rejectMessage = post.querySelector('textarea.reject-reason').value.toString().trim(); + reviewRow.innerHTML = '.'; + const args = { + postid: postID, + reason: rejectMessage ? rejectMessage : null + }; + const nextPostURL = await Ajax.call([{ + methodname: 'mod_moodleoverflow_review_reject_post', + args: args + }])[0]; + + let message = await getString('post_was_rejected', 'mod_moodleoverflow') + ' '; + if (nextPostURL) { + message += `` + + await getString('jump_to_next_post_needing_review', 'mod_moodleoverflow') + + ""; + } else { + message += await getString('there_are_no_posts_needing_review', 'mod_moodleoverflow'); + } + reviewRow.innerHTML = message; + } else if (action === 'reject-cancel') { + reviewRow.innerHTML = '.'; + reviewRow.innerHTML = await Templates.render('mod_moodleoverflow/review_buttons', {}); + } + }; +} \ No newline at end of file diff --git a/classes/output/email/renderer.php b/classes/output/email/renderer.php index e42807966f0..42a855a80ab 100644 --- a/classes/output/email/renderer.php +++ b/classes/output/email/renderer.php @@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../../../renderer.php'); +require_once($CFG->libdir . '/filelib.php'); /** * Moodleoverflow post renderable. @@ -55,7 +56,6 @@ public function moodleoverflow_email_template() { * @return string */ public function format_message_text($cm, $post) { - // Convert the message. $message = file_rewrite_pluginfile_urls( $post->message, 'pluginfile.php', diff --git a/classes/output/email/renderer_textemail.php b/classes/output/email/renderer_textemail.php index 7d359356442..4d8c72d29b2 100644 --- a/classes/output/email/renderer_textemail.php +++ b/classes/output/email/renderer_textemail.php @@ -24,6 +24,10 @@ namespace mod_moodleoverflow\output\email; +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/filelib.php'); + /** * Moodleoverflow post renderable. * @@ -51,7 +55,6 @@ public function moodleoverflow_email_template() { * @return string */ public function format_message_text($cm, $post) { - // Format the text. $message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', \context_module::instance($cm->id)->id, diff --git a/classes/ratings.php b/classes/ratings.php index 50105dff9ca..8e20b21b77a 100644 --- a/classes/ratings.php +++ b/classes/ratings.php @@ -82,7 +82,7 @@ public static function moodleoverflow_add_rating($moodleoverflow, $postid, $rati $coursecontext = \context_course::instance($course->id); // Redirect the user if capabilities are missing. - $canrate = self::moodleoverflow_user_can_rate($moodleoverflow, $cm, $modulecontext, $userid); + $canrate = self::moodleoverflow_user_can_rate($post, $modulecontext, $userid); if (!$canrate) { // Catch unenrolled users. @@ -784,13 +784,12 @@ private static function moodleoverflow_update_rating_record($postid, $rating, $u * Check if a user can rate the post. * * @param object $moodleoverflow - * @param null $cm - * @param null $modulecontext + * @param \context_module $modulecontext * @param null|int $userid * * @return bool */ - private static function moodleoverflow_user_can_rate($moodleoverflow, $cm = null, $modulecontext = null, $userid = null) { + private static function moodleoverflow_user_can_rate($post, $modulecontext, $userid = null) { global $USER; if (!$userid) { // Guests and non-logged-in users can not rate. @@ -800,20 +799,8 @@ private static function moodleoverflow_user_can_rate($moodleoverflow, $cm = null $userid = $USER->id; } - // Retrieve the coursemodule. - if (!$cm) { - if (!$cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id, $moodleoverflow->course)) { - throw new moodle_exception('invalidcoursemodule'); - } - } - - // Get the context if not set in the parameters. - if (!$modulecontext) { - $modulecontext = context_module::instance($cm->id); - } - // Check the capability. - return has_capability('mod/moodleoverflow:ratepost', $modulecontext, $userid); + return has_capability('mod/moodleoverflow:ratepost', $modulecontext, $userid) && $post->reviewed == 1; } } diff --git a/classes/readtracking.php b/classes/readtracking.php index 44b65db2da3..f037f064373 100644 --- a/classes/readtracking.php +++ b/classes/readtracking.php @@ -24,6 +24,7 @@ namespace mod_moodleoverflow; +use context_module; use moodle_exception; /** @@ -136,7 +137,7 @@ public static function moodleoverflow_mark_moodleoverflow_read($cm, $userid = nu foreach ($discussions as $discussionid => $amount) { // Mark the discussion as read. - if (!self::moodleoverflow_mark_discussion_read($discussionid, $userid)) { + if (!self::moodleoverflow_mark_discussion_read($discussionid, context_module::instance($cm->id), $userid)) { throw new moodle_exception('markreadfailed', 'moodleoverflow'); return false; @@ -150,13 +151,14 @@ public static function moodleoverflow_mark_moodleoverflow_read($cm, $userid = nu * Marks a specific discussion as read by a specific user. * * @param int $discussionid + * @param context_module $modcontext * @param null $userid */ - public static function moodleoverflow_mark_discussion_read($discussionid, $userid = null) { + public static function moodleoverflow_mark_discussion_read($discussionid, $modcontext, $userid = null) { global $USER; // Get all posts. - $posts = moodleoverflow_get_all_discussion_posts($discussionid, true); + $posts = moodleoverflow_get_all_discussion_posts($discussionid, true, $modcontext); // If no user is submitted, use the current one. if (!isset($userid)) { @@ -468,101 +470,36 @@ public static function get_untracked_moodleoverflows($userid, $courseid) { * * @return int|mixed */ - public static function moodleoverflow_count_unread_posts_moodleoverflow($cm, $course) { - global $CFG, $DB, $USER; - - // Create a cache. - static $readcache = array(); - - // Get the moodleoverflow ids. - $moodleoverflowid = $cm->instance; - - // Check whether the cache is already set. - if (!isset($readcache[$course->id])) { - - // Create a cache for the course. - $readcache[$course->id] = array(); - - // Count the unread posts in the course. - $counts = self::moodleoverflow_count_unread_posts_course($USER->id, $course->id); - if ($counts) { - - // Loop through all unread posts. - foreach ($counts as $count) { - $readcache[$course->id][$count->id] = $count->unread; - } - } - } - - // Check whether there are no unread post for this moodleoverflow. - if (empty($readcache[$course->id][$moodleoverflowid])) { + public static function moodleoverflow_count_unread_posts_moodleoverflow($cm) { + global $DB, $USER; + + $moodleoverflow = $DB->get_record_sql("SELECT m.*, tm.id as hasdisabledtracking " . + "FROM {moodleoverflow} m " . + "LEFT JOIN {moodleoverflow_tracking} tm ON m.id = tm.moodleoverflowid AND tm.userid = :userid " . + "WHERE m.id = :moodleoverflowid", ['userid' => $USER->id, 'moodleoverflowid' => $cm->instance]); + + // Return if tracking is off, or ((optional or forced, but forced disallowed by admin) and user has disabled tracking). + if ($moodleoverflow->trackingtype == MOODLEOVERFLOW_TRACKING_OFF || ( + ($moodleoverflow->trackingtype == MOODLEOVERFLOW_TRACKING_OPTIONAL || ( + $moodleoverflow->trackingtype == MOODLEOVERFLOW_TRACKING_FORCED && + !get_config('moodleoverflow', 'allowforcedreadtracking') + ) + ) && $moodleoverflow->hasdisabledtracking)) { return 0; } - - // Require the course library. - require_once($CFG->dirroot . '/course/lib.php'); - // Get the current timestamp and the cutoffdate. - $now = round(time(), -2); + $now = round(time(), -2); $cutoffdate = $now - (get_config('moodleoverflow', 'oldpostdays') * 24 * 60 * 60); // Define a sql-query. - $params = array($USER->id, $moodleoverflowid, $cutoffdate); + $params = array($USER->id, $cm->instance, $cutoffdate); $sql = "SELECT COUNT(p.id) FROM {moodleoverflow_posts} p JOIN {moodleoverflow_discussions} d ON p.discussion = d.id LEFT JOIN {moodleoverflow_read} r ON (r.postid = p.id AND r.userid = ?) - WHERE d.moodleoverflow = ? AND p.modified >= ? AND r.id IS NULL"; + WHERE d.moodleoverflow = ? AND p.modified >= ? AND r.id IS NULL AND p.reviewed = 1"; // Return the number of unread posts per moodleoverflow. return $DB->get_field_sql($sql, $params); } - - /** - * Get an array of unread posts within a course. - * - * @param int $userid The user ID - * @param int $courseid The course ID - * - * @return array Array of unread posts within a course - */ - public static function moodleoverflow_count_unread_posts_course($userid, $courseid) { - global $DB; - - // Get the current timestamp and calculate the cutoffdate. - $now = round(time(), -2); - $cutoffdate = $now - (get_config('moodleoverflow', 'oldpostdays') * 24 * 60 * 60); - - // Set parameters for the sql-query. - $params = array($userid, $userid, $courseid, $cutoffdate, $userid); - - // Check if forced readtracking is allowed. - if (get_config('moodleoverflow', 'allowforcedreadtracking')) { - $trackingsql = "AND (m.trackingtype = " . MOODLEOVERFLOW_TRACKING_FORCED . - " OR (m.trackingtype = " . MOODLEOVERFLOW_TRACKING_OPTIONAL . " AND tm.id IS NULL))"; - } else { - $trackingsql = "AND ((m.trackingtype = " . MOODLEOVERFLOW_TRACKING_OPTIONAL . " OR m.trackingtype = " . - MOODLEOVERFLOW_TRACKING_FORCED . ") AND tm.id IS NULL)"; - } - - // Define the sql-query. - $sql = "SELECT m.id, COUNT(p.id) AS unread - FROM {moodleoverflow_posts} p - JOIN {moodleoverflow_discussions} d ON d.id = p.discussion - JOIN {moodleoverflow} m ON m.id = d.moodleoverflow - JOIN {course} c ON c.id = m.course - LEFT JOIN {moodleoverflow_read} r ON (r.postid = p.id AND r.userid = ?) - LEFT JOIN {moodleoverflow_tracking} tm ON (tm.userid = ? AND tm.moodleoverflowid = m.id) - WHERE m.course = ? AND p.modified >= ? AND r.id IS NULL $trackingsql - GROUP BY m.id"; - - // Get the amount of unread post within a course. - $return = $DB->get_records_sql($sql, $params); - if ($return) { - return $return; - } - - // Else return nothing. - return array(); - } } diff --git a/classes/review.php b/classes/review.php new file mode 100644 index 00000000000..2a72a0d7605 --- /dev/null +++ b/classes/review.php @@ -0,0 +1,165 @@ +. + +/** + * Moodleoverflow anonymous related class. + * + * @package mod_moodleoverflow + * @copyright 2021 Justus Dieckmann WWU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_moodleoverflow; + +/** + * Class for Moodleoverflow anonymity + * + * @package mod_moodleoverflow + * @copyright 2021 Justus Dieckmann WWU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class review { + + /** + * Nothing has to be reviewed. + */ + const NOTHING = 0; + /** + * New questions (= discussions) have to be reviewed. + */ + const QUESTIONS = 1; + /** + * Everything has to be reviewed. + */ + const EVERYTHING = 2; + + /** + * Returns the review level of the given moodleoverflow instance, considering the global allowreview setting. + * @param object $moodleoverflow + * @return int + */ + public static function get_review_level(object $moodleoverflow): int { + if (get_config('moodleoverflow', 'allowreview') == '1') { + return $moodleoverflow->needsreview; + } else { + return self::NOTHING; + } + } + + /** + * Returns a short review info for the discussion. + * @param int $discussionid The discussionid. + * @return object {"count": amount needing review (int) , "first": first postid needing review (int)} + */ + public static function get_short_review_info_for_discussion(int $discussionid) { + global $DB; + + return $DB->get_record_sql( + 'SELECT COUNT(*) as count, MIN(id) AS first ' . + 'FROM {moodleoverflow_posts} ' . + 'WHERE discussion = :discussionid AND reviewed = 0 AND created < :cutofftime', [ + 'discussionid' => $discussionid, + 'cutofftime' => time() - get_config('moodleoverflow', 'reviewpossibleaftertime') + ] + ); + } + + /** + * @param int $moodleoverflowid ID of moodleoverflow to look in. + * @param int $afterpostid ID of post after which to look for the first post to review. + * @return string|null + */ + public static function get_first_review_post($moodleoverflowid, $afterpostid = null) { + global $DB; + + $params = ['moodleoverflowid' => $moodleoverflowid]; + $orderby = ''; + $addwhere = ''; + + if ($afterpostid) { + $afterdiscussionid = $DB->get_field('moodleoverflow_posts', 'discussion', ['id' => $afterpostid], + MUST_EXIST); + + $orderby = 'CASE WHEN (p.discussion > :afterdiscussionid OR (p.discussion = :afterdiscussionid2 AND p.id > :afterpostid)) THEN 0 ELSE 1 END, '; + $params['afterdiscussionid'] = $afterdiscussionid; + $params['afterdiscussionid2'] = $afterdiscussionid; + $params['afterpostid'] = $afterpostid; + + $addwhere = ' AND p.id != :notpostid '; + $params['notpostid'] = $afterpostid; + } + $record = $DB->get_record_sql( + 'SELECT p.id as postid, p.discussion as discussionid FROM {moodleoverflow_posts} p ' . + 'JOIN {moodleoverflow_discussions} d ON d.id = p.discussion ' . + "WHERE p.reviewed = 0 AND d.moodleoverflow = :moodleoverflowid $addwhere " . + "ORDER BY $orderby p.discussion, p.id " . + 'LIMIT 1', + $params + ); + if ($record) { + return (new \moodle_url('/mod/moodleoverflow/discussion.php', [ + 'd' => $record->discussionid + ], 'p' . $record->postid))->out(false); + } else { + return null; + } + } + + /** + * Return if the post does need/needed a review with the current moodleoverflow settings. + * @param object $post + * @param object $moodleoverflow + * @return bool + */ + public static function should_post_be_reviewed($post, $moodleoverflow): bool { + $reviewlevel = self::get_review_level($moodleoverflow); + if ($post->parent) { + return $reviewlevel == self::EVERYTHING; + } else { + return $reviewlevel >= self::QUESTIONS; + } + } + + /** + * Returns whether a post is reviewable depending on its review state and review period. + * @param object $post + * @return bool + */ + public static function is_post_in_review_period($post): bool { + return time() - $post->created > get_config('moodleoverflow', 'reviewpossibleaftertime'); + } + + /** + * Count outstanding reviews in the moodleoverflow. + * + * @param $moodleoverflowid + * @return int + */ + public static function count_outstanding_reviews_in_moodleoverflow($moodleoverflowid): int { + global $DB; + return $DB->count_records_sql( + 'SELECT COUNT(*) ' . + 'FROM {moodleoverflow_posts} p ' . + 'JOIN {moodleoverflow_discussions} d ON d.id = p.discussion ' . + 'WHERE d.moodleoverflow = :moodleoverflowid AND p.created < :cutofftime AND reviewed = 0', [ + 'moodleoverflowid' => $moodleoverflowid, + 'cutofftime' => time() - get_config('moodleoverflow', 'reviewpossibleaftertime') + ] + ); + } + + +} diff --git a/classes/task/send_mails.php b/classes/task/send_mails.php index 353bf89181c..0d4366154ba 100644 --- a/classes/task/send_mails.php +++ b/classes/task/send_mails.php @@ -24,6 +24,9 @@ namespace mod_moodleoverflow\task; +use core\session\exception; +use mod_moodleoverflow\output\moodleoverflow_email; + defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../../locallib.php'); @@ -53,10 +56,108 @@ public function execute() { // Send mail notifications. moodleoverflow_send_mails(); + $this->send_review_notifications(); + // The cron is finished. return true; } + /** + * Sends initial notifications for needed reviews to all users with review capability. + */ + public function send_review_notifications() { + global $DB, $OUTPUT, $PAGE; + + $rendererhtml = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'htmlemail'); + $renderertext = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'textemail'); + + $postinfos = $DB->get_records_sql( + 'SELECT p.*, d.course as cid, d.moodleoverflow as mid, d.id as did FROM {moodleoverflow_posts} p ' . + 'JOIN {moodleoverflow_discussions} d ON p.discussion = d.id ' . + "WHERE p.mailed = :mailpending AND p.reviewed = 0 AND p.created < :timecutoff " . + "ORDER BY d.course, d.moodleoverflow, d.id", + [ + 'mailpending' => MOODLEOVERFLOW_MAILED_PENDING, + 'timecutoff' => time() - get_config('moodleoverflow', 'reviewpossibleaftertime') + ] + ); + + if (empty($postinfos)) { + mtrace('No review notifications to send.'); + return; + } + + $course = null; + + $moodleoverflow = null; + $usersto = null; + $cm = null; + + $discussion = null; + + $success = []; + + foreach ($postinfos as $postinfo) { + if ($course == null || $course->id != $postinfo->cid) { + $course = get_course($postinfo->cid); + } + + if ($moodleoverflow == null || $moodleoverflow->id != $postinfo->mid) { + $cm = get_coursemodule_from_instance('moodleoverflow', $postinfo->mid, 0, false, MUST_EXIST); + $modulecontext = \context_module::instance($cm->id); + $usersto = get_users_by_capability($modulecontext, 'mod/moodleoverflow:reviewpost'); + $moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $postinfo->mid], '*', MUST_EXIST); + } + + if ($discussion == null || $discussion->id != $postinfo->did) { + $discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $postinfo->did], '*', MUST_EXIST); + } + + $post = $postinfo; + $userfrom = \core_user::get_user($postinfo->userid, '*', MUST_EXIST); + + foreach ($usersto as $userto) { + try { + cron_setup_user($userto, $course); + + $maildata = new moodleoverflow_email( + $course, + $cm, + $moodleoverflow, + $discussion, + $post, + $userfrom, + $userto, + false + ); + + $textcontext = $maildata->export_for_template($renderertext, true); + $htmlcontext = $maildata->export_for_template($rendererhtml, false); + + email_to_user( + $userto, + \core_user::get_noreply_user(), + get_string('email_review_needed_subject', 'moodleoverflow', $textcontext), + $OUTPUT->render_from_template('mod_moodleoverflow/email_review_needed_text', $textcontext), + $OUTPUT->render_from_template('mod_moodleoverflow/email_review_needed_html', $htmlcontext) + ); + } catch (exception $e) { + mtrace("Error sending review notification for post $post->id to user $userto->id!"); + } + } + $success[] = $post->id; + } + + if (!empty($success)) { + list($insql, $inparams) = $DB->get_in_or_equal($success); + $DB->set_field_select( + 'moodleoverflow_posts', 'mailed', MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS, + 'id ' . $insql, $inparams + ); + mtrace('Sent review notifications for ' . count($success) . ' posts successfully!'); + } + } + } diff --git a/db/access.php b/db/access.php index 16f3ca34f9a..17259912da9 100644 --- a/db/access.php +++ b/db/access.php @@ -234,4 +234,15 @@ 'clonepermissionsfrom' => 'mod/forum:createattachment' ), + 'mod/moodleoverflow:reviewpost' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/course:manageactivities' + ), + ); diff --git a/db/install.xml b/db/install.xml index 31ba26467c5..2b787b61add 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -26,6 +26,7 @@ + @@ -66,7 +67,9 @@ - + + + diff --git a/db/services.php b/db/services.php index 7bcba3e5eba..e1c585e6303 100644 --- a/db/services.php +++ b/db/services.php @@ -33,5 +33,21 @@ 'type' => 'write', 'ajax' => true, 'capabilities' => 'mod/moodleoverflow:ratepost' - ) + ), + 'mod_moodleoverflow_review_approve_post' => array( + 'classname' => 'mod_moodleoverflow_external', + 'methodname' => 'review_approve_post', + 'classpath' => 'mod/moodleoverflow/externallib.php', + 'description' => 'Approves a post', + 'type' => 'write', + 'ajax' => true, + ), + 'mod_moodleoverflow_review_reject_post' => array( + 'classname' => 'mod_moodleoverflow_external', + 'methodname' => 'review_reject_post', + 'classpath' => 'mod/moodleoverflow/externallib.php', + 'description' => 'Rejects a post', + 'type' => 'write', + 'ajax' => true, + ), ); diff --git a/db/upgrade.php b/db/upgrade.php index f8aac2eec32..cb930b63482 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -195,5 +195,40 @@ function xmldb_moodleoverflow_upgrade($oldversion) { upgrade_mod_savepoint(true, 2021111700, 'moodleoverflow'); } + if ($oldversion < 2022072000) { + + // Define field needsreview to be added to moodleoverflow. + $table = new xmldb_table('moodleoverflow'); + $field = new xmldb_field('needsreview', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'anonymous'); + + // Conditionally launch add field needsreview. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field reviewed and timereviewed to be added to moodleoverflow_posts. + $table = new xmldb_table('moodleoverflow_posts'); + $field = new xmldb_field('reviewed', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'mailed'); + + // Conditionally launch add field reviewed. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $field = new xmldb_field('timereviewed', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'reviewed'); + + // Conditionally launch add field timereviewed. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $field = new xmldb_field('mailed', XMLDB_TYPE_INTEGER, '2', null, null, null, '0', 'attachment'); + // Launch change of precision for field mailed. + $dbman->change_field_precision($table, $field); + + // Moodleoverflow savepoint reached. + upgrade_mod_savepoint(true, 2022072000, 'moodleoverflow'); + } + return true; } diff --git a/discussion.php b/discussion.php index cd162d5fb1f..c98beee054d 100644 --- a/discussion.php +++ b/discussion.php @@ -129,6 +129,8 @@ $node->add(format_string($post->subject), $PAGE->url); } +$PAGE->requires->js_call_amd('mod_moodleoverflow/reviewing', 'init'); + // Initiate the page. $PAGE->set_title($course->shortname . ': ' . format_string($discussion->name)); $PAGE->set_heading($course->fullname); @@ -146,18 +148,12 @@ echo ''; } -// Check if the user can reply in this discussion. -$canreply = moodleoverflow_user_can_post($moodleoverflow, $USER, $cm, $course, $modulecontext); +echo "
"; -// Link to the selfenrollment if not allowed. -if (!$canreply) { - if (!is_enrolled($modulecontext) AND !is_viewing($modulecontext)) { - $canreply = enrol_selfenrol_available($course->id); - } -} +echo '
'; -echo "
"; +moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discussion, $post); -moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discussion, $post, $canreply); +echo '
'; echo $OUTPUT->footer(); diff --git a/externallib.php b/externallib.php index 1b56d144db6..91f8b1f8860 100644 --- a/externallib.php +++ b/externallib.php @@ -22,9 +22,13 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use mod_moodleoverflow\output\moodleoverflow_email; +use mod_moodleoverflow\review; + defined('MOODLE_INTERNAL') || die; require_once("$CFG->libdir/externallib.php"); +require_once($CFG->dirroot . '/mod/moodleoverflow/locallib.php'); /** * Class implementing the external API, esp. for AJAX functions. @@ -143,4 +147,161 @@ public static function record_vote($discussionid, $postid, $ratingid, $sesskey) return $params; } + + /** + * Returns description of method parameters. + * @return external_function_parameters + */ + public static function review_approve_post_parameters() { + return new external_function_parameters([ + 'postid' => new external_value(PARAM_INT, 'id of post') + ]); + } + + /** + * Returns description of return value. + * @return external_value + */ + public static function review_approve_post_returns() { + return new external_value(PARAM_TEXT, 'the url of the next post to review'); + } + + /** + * Approve a post. + * + * @param int $postid ID of post to approve. + * @return string|null Url of next post to review. + */ + public static function review_approve_post($postid) { + global $DB; + + $params = self::validate_parameters(self::review_approve_post_parameters(), ['postid' => $postid]); + $postid = $params['postid']; + + $post = $DB->get_record('moodleoverflow_posts', ['id' => $postid], '*', MUST_EXIST); + $discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $post->discussion], '*', MUST_EXIST); + $moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $discussion->moodleoverflow], '*', MUST_EXIST); + + $cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id); + $context = context_module::instance($cm->id); + + require_capability('mod/moodleoverflow:reviewpost', $context); + + if ($post->reviewed) { + throw new coding_exception('post was already approved!'); + } + + if (!review::is_post_in_review_period($post)) { + throw new coding_exception('post is not yet in review period!'); + } + + $post->reviewed = 1; + $post->timereviewed = time(); + + $DB->update_record('moodleoverflow_posts', $post); + + if ($post->modified > $discussion->timemodified) { + $discussion->timemodified = $post->modified; + $discussion->usermodified = $post->userid; + $DB->update_record('moodleoverflow_discussions', $discussion); + } + + return review::get_first_review_post($moodleoverflow->id, $post->id); + } + + /** + * Returns description of method parameters. + * @return external_function_parameters + */ + public static function review_reject_post_parameters() { + return new external_function_parameters([ + 'postid' => new external_value(PARAM_INT, 'id of post'), + 'reason' => new external_value(PARAM_RAW, 'reason of rejection') + ]); + } + + /** + * Returns description of return value. + * @return external_value + */ + public static function review_reject_post_returns() { + return new external_value(PARAM_TEXT, 'the url of the next post to review'); + } + + /** + * Rejects a post. + * + * @param int $postid ID of post to reject. + * @param string|null $reason The reason for rejection. + * @return string|null Url of next post to review. + */ + public static function review_reject_post($postid, $reason = null) { + global $DB, $PAGE, $OUTPUT; + + $params = self::validate_parameters(self::review_reject_post_parameters(), ['postid' => $postid, 'reason' => $reason]); + $postid = $params['postid']; + + $post = $DB->get_record('moodleoverflow_posts', ['id' => $postid], '*', MUST_EXIST); + $discussion = $DB->get_record('moodleoverflow_discussions', ['id' => $post->discussion], '*', MUST_EXIST); + $moodleoverflow = $DB->get_record('moodleoverflow', ['id' => $discussion->moodleoverflow], '*', MUST_EXIST); + $cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id); + $course = get_course($cm->course); + $context = context_module::instance($cm->id); + + $PAGE->set_context($context); + + require_capability('mod/moodleoverflow:reviewpost', $context); + + if ($post->reviewed) { + throw new coding_exception('post was already approved!'); + } + + if (!review::is_post_in_review_period($post)) { + throw new coding_exception('post is not yet in review period!'); + } + + // Has to be done before deleting the post. + $rendererhtml = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'htmlemail'); + $renderertext = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'textemail'); + + $userto = core_user::get_user($post->userid); + + $maildata = new moodleoverflow_email( + $course, + $cm, + $moodleoverflow, + $discussion, + $post, + $userto, + $userto, + false + ); + + $textcontext = $maildata->export_for_template($renderertext, true); + $htmlcontext = $maildata->export_for_template($rendererhtml, false); + + if ($params['reason'] ?? null) { + $htmlcontext['reason'] = format_text_email($params['reason'], FORMAT_PLAIN); + $textcontext['reason'] = $htmlcontext['reason']; + } + + email_to_user( + $userto, + \core_user::get_noreply_user(), + get_string('email_rejected_subject', 'moodleoverflow', $textcontext), + $OUTPUT->render_from_template('mod_moodleoverflow/email_rejected_text', $textcontext), + $OUTPUT->render_from_template('mod_moodleoverflow/email_rejected_html', $htmlcontext) + ); + + $url = review::get_first_review_post($moodleoverflow->id, $post->id); + + if (!$post->parent) { + // Delete discussion, if this is the question. + moodleoverflow_delete_discussion($discussion, $course, $cm, $moodleoverflow); + } else { + moodleoverflow_delete_post($post, true, $cm, $moodleoverflow); + } + + return $url; + } } diff --git a/index.php b/index.php index 63d159a73a7..716f499eaab 100644 --- a/index.php +++ b/index.php @@ -251,8 +251,7 @@ if (isset($untracked[$moodleoverflow->id])) { $unreadlink = '-'; - } else if ($unread = \mod_moodleoverflow\readtracking::moodleoverflow_count_unread_posts_moodleoverflow($cm, - $course) + } else if ($unread = \mod_moodleoverflow\readtracking::moodleoverflow_count_unread_posts_moodleoverflow($cm) ) { // There are unread posts in the moodleoverflow instance. diff --git a/lang/en/moodleoverflow.php b/lang/en/moodleoverflow.php index 5c6f5154c64..4b6f937df5d 100644 --- a/lang/en/moodleoverflow.php +++ b/lang/en/moodleoverflow.php @@ -65,7 +65,7 @@ $string['headerdiscussion'] = 'Discussion'; $string['headerstartedby'] = 'Started by'; $string['headerreplies'] = 'Replies'; -$string['headerlastpost'] = 'Last post'; +$string['headerlastpost'] = 'Last published post'; $string['headerunread'] = 'Unread'; $string['headervotes'] = 'Votes'; $string['headerstatus'] = 'Status'; @@ -92,7 +92,7 @@ $string['maxattachments_help'] = 'This setting specifies the maximum number of files that can be attached to a forum post.'; $string['configmaxattachments'] = 'Default maximum number of attachments allowed per post.'; $string['maxeditingtime'] = 'Maximum amount of time during which a post can be edited by its owner (sec)'; -$string['configmaxeditingtime'] = 'Default maximum seconds are 3600 (= one hour).'; +$string['configmaxeditingtime'] = 'Default maximum seconds are 3600 (= one hour). Regarding editing posts, please also consider the "Review possible after" setting for moderated forums.'; $string['configoldpostdays'] = 'Number of days old any post is considered read.'; $string['oldpostdays'] = 'Read after days'; $string['trackingoff'] = 'Off'; @@ -418,3 +418,34 @@ $string['desc:only_questions'] = 'The name of questioners will not be displayed in their question and comments.'; $string['desc:anonymous'] = 'No names will be displayed.'; $string['resetanonymous_warning'] = 'Are you sure? If you are in production, this is most certainly a bad decision because your students and teachers posted their questions and answers, believing they would remain anonymous.

{$a->fullanoncount} forums are currently fully anonymized, and in {$a->questionanoncount} additional forums the questioners are anonymized.

In all these forums, the real names of posters will be displayed again, even in already existing posts!

There is no way of reverting those changes!
'; + +// Review feature +$string['review'] = 'Review'; +$string['review_help'] = 'Select what has to be approved by a teacher before being shown to students.'; +$string['nothing'] = 'Nothing'; +$string['questions'] = 'Questions'; +$string['questions_and_posts'] = 'Questions and answers'; +$string['desc:review_questions'] = 'All questions are going to be reviewed by a teacher before being published.'; +$string['desc:review_everything'] = 'All questions and answers are going to be reviewed by a teacher before being published.'; +$string['allowreview'] = 'Allow moderated forums'; +$string['allowreview_desc'] = 'Allow teachers to enable that all posts (or only all questions) have to be reviewed by them in order to be published.'; +$string['amount_waiting_for_review'] = '{$a} post(s) need to be reviewed!'; +$string['pending_review'] = 'Pending review'; +$string['post_was_approved'] = 'The post was approved.'; +$string['post_was_rejected'] = 'The post was rejected.'; +$string['jump_to_next_post_needing_review'] = 'Jump to next post needing to be reviewed.'; +$string['there_are_no_posts_needing_review'] = 'There are no more posts in this forum that need to be reviewed.'; +$string['give_a_reason'] = 'Give a reason (optional)'; +$string['approve'] = 'Approve'; +$string['reject'] = 'Reject'; +$string['reviewpossibleaftertime'] = 'Review possible after (secs)'; +$string['reviewpossibleaftertime_desc'] = 'A teacher can only reject or approve a post at least this amount of time (in seconds) after the creation of the post. After a teacher has approved a post, the post cannot be edited by it\'s author anymore, even if still within the maxeditingtime duration.'; +$string['pending_review_but_cannot_now'] = 'Pending review, but can only be approved at least {$a} after the creation of this post to allow the author a bit of time to edit it.'; + +$string['review_needed'] = 'Review needed!'; + +$string['email_review_needed_subject'] = 'Review needed in {$a->coursename}: {$a->subject}'; +$string['email_rejected_subject'] = '{$a->coursename}: One of your posts has been rejected.'; +$string['your_post_was_rejected'] = 'Your post was rejected.'; +$string['your_post_was_rejected_with_reason'] = 'Your post was rejected with the following reason:'; +$string['original_post'] = 'Original post'; diff --git a/lib.php b/lib.php index d29fa21dae4..b3d21e50ce3 100644 --- a/lib.php +++ b/lib.php @@ -48,6 +48,7 @@ define('MOODLEOVERFLOW_MAILED_PENDING', 0); define('MOODLEOVERFLOW_MAILED_SUCCESS', 1); define('MOODLEOVERFLOW_MAILED_ERROR', 2); +define('MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS', 3); // Constants for the post rating. define('MOODLEOVERFLOW_PREFERENCE_STARTER', 0); @@ -829,7 +830,7 @@ function moodleoverflow_send_mails() { $modulecontext = context_module::instance($cm->id); // Check the users capabilities. - $canpost = moodleoverflow_user_can_post($moodleoverflow, $userto, $cm, $course, $modulecontext); + $canpost = moodleoverflow_user_can_post($modulecontext, $post, $userto->id); $userto->canpost[$discussion->id] = $canpost; } @@ -863,7 +864,7 @@ function moodleoverflow_send_mails() { // Cache the users capabilities. if (!isset($userto->canpost[$discussion->id])) { - $canreply = moodleoverflow_user_can_post($moodleoverflow, $userto, $cm, $course, $modulecontext); + $canreply = moodleoverflow_user_can_post($modulecontext, $post, $userto->id); } else { $canreply = $userto->canpost[$discussion->id]; } @@ -1005,15 +1006,18 @@ function moodleoverflow_get_unmailed_posts($starttime, $endtime) { // Set params for the sql query. $params = array(); - $params['mailed'] = MOODLEOVERFLOW_MAILED_PENDING; $params['ptimestart'] = $starttime; $params['ptimeend'] = $endtime; + $pendingmail = MOODLEOVERFLOW_MAILED_PENDING; + $reviewsent = MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS; + // Retrieve the records. $sql = "SELECT p.*, d.course, d.moodleoverflow FROM {moodleoverflow_posts} p JOIN {moodleoverflow_discussions} d ON d.id = p.discussion - WHERE p.mailed = :mailed AND p.created >= :ptimestart AND p.created < :ptimeend + WHERE p.mailed IN ($pendingmail, $reviewsent) AND p.reviewed = 1 + AND COALESCE(p.timereviewed, p.created) >= :ptimestart AND p.created < :ptimeend ORDER BY p.modified ASC"; return $DB->get_records_sql($sql, $params); @@ -1035,6 +1039,7 @@ function moodleoverflow_mark_old_posts_as_mailed($endtime) { // Define variables for the sql query. $params = array(); $params['mailedsuccess'] = MOODLEOVERFLOW_MAILED_SUCCESS; + $params['mailedreviewsent'] = MOODLEOVERFLOW_MAILED_REVIEW_SUCCESS; $params['now'] = $now; $params['endtime'] = $endtime; $params['mailedpending'] = MOODLEOVERFLOW_MAILED_PENDING; @@ -1042,7 +1047,7 @@ function moodleoverflow_mark_old_posts_as_mailed($endtime) { // Define the sql query. $sql = "UPDATE {moodleoverflow_posts} SET mailed = :mailedsuccess - WHERE (created < :endtime) AND mailed = :mailedpending"; + WHERE (created < :endtime) AND mailed IN (:mailedpending, :mailedreviewsent) AND reviewed = 1"; return $DB->execute($sql, $params); @@ -1075,27 +1080,37 @@ function moodleoverflow_minimise_user_record(stdClass $user) { function moodleoverflow_cm_info_view(cm_info $cm) { $cantrack = \mod_moodleoverflow\readtracking::moodleoverflow_can_track_moodleoverflows(); + $out = ""; + if (has_capability('mod/moodleoverflow:reviewpost', $cm->context)) { + $reviewcount = \mod_moodleoverflow\review::count_outstanding_reviews_in_moodleoverflow($cm->instance); + if ($reviewcount) { + $out .= ''; + $out .= get_string('amount_waiting_for_review', 'mod_moodleoverflow', $reviewcount); + $out .= ' '; + } + } if ($cantrack) { - $unread = \mod_moodleoverflow\readtracking::moodleoverflow_count_unread_posts_moodleoverflow($cm, - $cm->get_course()); + $unread = \mod_moodleoverflow\readtracking::moodleoverflow_count_unread_posts_moodleoverflow($cm); if ($unread) { - $out = ' '; + $out .= ' '; if ($unread == 1) { $out .= get_string('unreadpostsone', 'moodleoverflow'); } else { $out .= get_string('unreadpostsnumber', 'moodleoverflow', $unread); } $out .= ''; - $cm->set_after_link($out); } } + if ($out) { + $cm->set_after_link($out); + } } /** * Check if the user can create attachments in moodleoverflow. * * @param stdClass $moodleoverflow moodleoverflow object - * @param stdClass $context context object + * @param context_module $context context object * * @return bool true if the user can create attachments, false otherwise * @since Moodle 3.3 @@ -1210,3 +1225,13 @@ function moodleoverflow_grade_item_update($moodleoverflow, $grades=null) { return $gradeupdate; } + +/** + * Map icons for font-awesome themes. + */ +function moodleoverflow_get_fontawesome_icon_map() { + return [ + 'mod_moodleoverflow:i/commenting' => 'fa-commenting', + 'mod_moodleoverflow:i/pending-big' => 'fa-clock-o text-danger moodleoverflow-icon-big' + ]; +} diff --git a/locallib.php b/locallib.php index ecd379ea57a..b6e04cc208f 100644 --- a/locallib.php +++ b/locallib.php @@ -26,6 +26,7 @@ */ use mod_moodleoverflow\anonymous; +use mod_moodleoverflow\review; defined('MOODLE_INTERNAL') || die(); @@ -41,9 +42,7 @@ * @return array */ function moodleoverflow_get_discussions($cm, $page = -1, $perpage = 0) { - global $DB, $CFG; - - $params = array($cm->instance); + global $DB, $CFG, $USER; // User must have the permission to view the discussions. $modcontext = context_module::instance($cm->id); @@ -69,7 +68,7 @@ function moodleoverflow_get_discussions($cm, $page = -1, $perpage = 0) { } else { $allnames = get_all_user_name_fields(true, 'u'); } - $postdata = 'p.id, p.modified, p.discussion, p.userid'; + $postdata = 'p.id, p.modified, p.discussion, p.userid, p.reviewed'; $discussiondata = 'd.name, d.timemodified, d.timestart, d.usermodified'; $userdata = 'u.email, u.picture, u.imagealt'; @@ -81,15 +80,24 @@ function moodleoverflow_get_discussions($cm, $page = -1, $perpage = 0) { $usermodifiedfields = get_all_user_name_fields(true, 'um', null, 'um') . ', um.email AS umemail, um.picture AS umpicture, um.imagealt AS umimagealt'; } - $usermodifiedtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)"; + + $params = [$cm->instance]; + $whereconditions = ['d.moodleoverflow = ?', 'p.parent = 0']; + + if (!has_capability('mod/moodleoverflow:reviewpost', $modcontext)) { + $whereconditions[] = '(p.reviewed = 1 OR p.userid = ?)'; + $params[] = $USER->id; + } + + $wheresql = join(' AND ', $whereconditions); // Retrieve and return all discussions from the database. $sql = "SELECT $postdata, $discussiondata, $allnames, $userdata, $usermodifiedfields FROM {moodleoverflow_discussions} d JOIN {moodleoverflow_posts} p ON p.discussion = d.id LEFT JOIN {user} u ON p.userid = u.id - $usermodifiedtable - WHERE d.moodleoverflow = ? AND p.parent = 0 + LEFT JOIN {user} um ON d.usermodified = um.id + WHERE $wheresql ORDER BY d.timestart DESC, d.id DESC"; return $DB->get_records_sql($sql, $params, $limitfrom, $limitamount); @@ -125,6 +133,7 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - // Check some capabilities. $canstartdiscussion = moodleoverflow_user_can_post_discussion($moodleoverflow, $cm, $context); $canviewdiscussions = has_capability('mod/moodleoverflow:viewdiscussion', $context); + $canreviewposts = has_capability('mod/moodleoverflow:reviewpost', $context); // Print a button if the user is capable of starting // a new discussion or if the selfenrol is aviable. @@ -132,7 +141,7 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - $buttontext = get_string('addanewdiscussion', 'moodleoverflow'); $buttonurl = new moodle_url('/mod/moodleoverflow/post.php', ['moodleoverflow' => $moodleoverflow->id]); $button = new single_button($buttonurl, $buttontext, 'get'); - $button->class = 'singlebutton moodleoverflowaddnew'; + $button->class = 'singlebutton align-middle m-2'; $button->formid = 'newdiscussionform'; echo $OUTPUT->render($button); } @@ -151,7 +160,7 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - } // Get the number of replies for each discussion. - $replies = moodleoverflow_count_discussion_replies($moodleoverflow->id); + $replies = moodleoverflow_count_discussion_replies($cm); // Check whether the moodleoverflow instance can be tracked and is tracked. if ($cantrack = \mod_moodleoverflow\readtracking::moodleoverflow_can_track_moodleoverflows($moodleoverflow)) { @@ -282,6 +291,7 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - // Get the amount of replies and the link to the discussion. $preparedarray[$i]['replyamount'] = $discussion->replies; + $preparedarray[$i]['questionunderreview'] = $discussion->reviewed == 0; // Are there unread messages? Create a link to them. $preparedarray[$i]['unreadamount'] = $discussion->unread; @@ -328,6 +338,14 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - } } + if ($canreviewposts) { + $reviewinfo = review::get_short_review_info_for_discussion($discussion->discussion); + $preparedarray[$i]['needreview'] = $reviewinfo->count; + $preparedarray[$i]['reviewlink'] = (new moodle_url('/mod/moodleoverflow/discussion.php', [ + 'd' => $discussion->discussion + ], 'p' . $reviewinfo->first))->out(false); + } + // Add all created data to an array. $preparedarray[$i]['statusstarter'] = $statusstarter; @@ -346,6 +364,7 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - $mustachedata = new stdClass(); $mustachedata->cantrack = $cantrack; $mustachedata->canviewdiscussions = $canviewdiscussions; + $mustachedata->canreview = $canreviewposts; $mustachedata->discussions = $preparedarray; $mustachedata->hasdiscussions = (count($discussions) >= 0) ? true : false; $mustachedata->istracked = $istracked; @@ -364,20 +383,32 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - /** * Returns an array of counts of replies for each discussion. * - * @param int $moodleoverflowid + * @param object $cm coursemodule * * @return array */ -function moodleoverflow_count_discussion_replies($moodleoverflowid) { - global $DB; +function moodleoverflow_count_discussion_replies($cm) { + global $DB, $USER; + + $modcontext = context_module::instance($cm->id); + + $params = [$cm->instance]; + $whereconditions = ['d.moodleoverflow = ?', 'p.parent > 0']; + + if (!has_capability('mod/moodleoverflow:reviewpost', $modcontext)) { + $whereconditions[] = '(p.reviewed = 1 OR p.userid = ?)'; + $params[] = $USER->id; + } + + $wheresql = join(' AND ', $whereconditions); $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid FROM {moodleoverflow_posts} p JOIN {moodleoverflow_discussions} d ON p.discussion = d.id - WHERE p.parent > 0 AND d.moodleoverflow = ? + WHERE $wheresql GROUP BY p.discussion"; - return $DB->get_records_sql($sql, array($moodleoverflowid)); + return $DB->get_records_sql($sql, $params); } /** @@ -424,14 +455,24 @@ function moodleoverflow_user_can_post_discussion($moodleoverflow, $cm = null, $c * @return array */ function moodleoverflow_get_discussions_count($cm) { - global $DB; + global $DB, $USER; + + $modcontext = context_module::instance($cm->id); - $params = array($cm->instance); + $params = [$cm->instance]; + $whereconditions = ['d.moodleoverflow = ?', 'p.parent = 0']; + + if (!has_capability('mod/moodleoverflow:reviewpost', $modcontext)) { + $whereconditions[] = '(p.reviewed = 1 OR p.userid = ?)'; + $params[] = $USER->id; + } + + $wheresql = join(' AND ', $whereconditions); $sql = 'SELECT COUNT(d.id) FROM {moodleoverflow_discussions} d JOIN {moodleoverflow_posts} p ON p.discussion = d.id - WHERE d.moodleoverflow = ? AND p.parent = 0'; + WHERE ' . $wheresql; return $DB->get_field_sql($sql, $params); } @@ -447,29 +488,40 @@ function moodleoverflow_get_discussions_unread($cm) { global $DB, $USER; // Get the current timestamp and the oldpost-timestamp. - $params = array(); $now = round(time(), -2); $cutoffdate = $now - (get_config('moodleoverflow', 'oldpostdays') * 24 * 60 * 60); + $modcontext = context_module::instance($cm->id); + + $whereconditions = ['d.moodleoverflow = :instance', 'p.modified >= :cutoffdate', 'r.id is NULL']; + $params = [ + 'userid' => $USER->id, + 'instance' => $cm->instance, + 'cutoffdate' => $cutoffdate + ]; + + if (!has_capability('mod/moodleoverflow:reviewpost', $modcontext)) { + $whereconditions[] = '(p.reviewed = 1 OR p.userid = :userid2)'; + $params['userid2'] = $USER->id; + } + + $wheresql = join(' AND ', $whereconditions); + // Define the sql-query. $sql = "SELECT d.id, COUNT(p.id) AS unread - FROM {moodleoverflow_discussions} d - JOIN {moodleoverflow_posts} p ON p.discussion = d.id - LEFT JOIN {moodleoverflow_read} r ON (r.postid = p.id AND r.userid = :userid) - WHERE d.moodleoverflow = :instance - AND p.modified >= :cutoffdate AND r.id is NULL - GROUP BY d.id"; - $params['userid'] = $USER->id; - $params['instance'] = $cm->instance; - $params['cutoffdate'] = $cutoffdate; + FROM {moodleoverflow_discussions} d + JOIN {moodleoverflow_posts} p ON p.discussion = d.id + LEFT JOIN {moodleoverflow_read} r ON (r.postid = p.id AND r.userid = :userid) + WHERE $wheresql + GROUP BY d.id"; // Return the unread messages as an array. if ($unreads = $DB->get_records_sql($sql, $params)) { + $returnarray = []; foreach ($unreads as $unread) { - $unreads[$unread->id] = $unread->unread; + $returnarray[$unread->id] = $unread->unread; } - - return $unreads; + return $returnarray; } else { // If there are no unread messages, return an empty array. @@ -516,11 +568,10 @@ function moodleoverflow_get_post_full($postid) { * @param object $discussion * @param object $post * @param object $cm - * @param null $user * * @return bool */ -function moodleoverflow_user_can_see_post($moodleoverflow, $discussion, $post, $cm, $user = null) { +function moodleoverflow_user_can_see_post($moodleoverflow, $discussion, $post, $cm) { global $USER, $DB; // Retrieve the modulecontext. @@ -570,7 +621,7 @@ function moodleoverflow_user_can_see_post($moodleoverflow, $discussion, $post, $ // Check if the user can view the discussion. $canviewdiscussion = !empty($cm->cache->caps['mod/moodleoverflow:viewdiscussion']) || - has_capability('mod/moodleoverflow:viewdiscussion', $modulecontext, $user->id); + has_capability('mod/moodleoverflow:viewdiscussion', $modulecontext, $user); if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), context_user::instance($post->userid)) @@ -578,6 +629,11 @@ function moodleoverflow_user_can_see_post($moodleoverflow, $discussion, $post, $ return false; } + if (!($post->reviewed == 1 || $post->userid == $user->id || + has_capability('mod/moodleoverflow:reviewpost', $modulecontext, $user))) { + return false; + } + // Check the coursemodule settings. if (isset($cm->uservisible)) { if (!$cm->uservisible) { @@ -640,7 +696,7 @@ function moodleoverflow_user_can_see_discussion($moodleoverflow, $discussion, $c * Creates a new moodleoverflow discussion. * * @param stdClass $discussion The discussion object - * @param object $modulecontext + * @param context_module $modulecontext * @param int $userid The user ID * * @return bool|int The id of the created discussion @@ -681,6 +737,12 @@ function moodleoverflow_add_discussion($discussion, $modulecontext, $userid = nu $post->moodleoverflow = $moodleoverflow->id; $post->course = $moodleoverflow->course; + // Set to not reviewed, if questions should be reviewed, and user is not a reviewer themselves. + if (review::get_review_level($moodleoverflow) >= review::QUESTIONS && + !has_capability('mod/moodleoverflow:reviewpost', $modulecontext, $userid)) { + $post->reviewed = 0; + } + // Submit the post to the database and get its id. $post->id = $DB->insert_record('moodleoverflow_posts', $post); @@ -754,50 +816,19 @@ function moodleoverflow_go_back_to($default) { * * @return bool Whether the user can reply */ -function moodleoverflow_user_can_post($moodleoverflow, $user = null, $cm = null, $course = null, $modulecontext = null) { - global $USER, $DB; +function moodleoverflow_user_can_post($modulecontext, $posttoreplyto, $considerreviewstatus = true, $userid = null) { + global $USER; // If not user is submitted, use the current one. - if (empty($user)) { - $user = $USER; - } - - // Guests can not post. - if (isguestuser($user) OR empty($user->id)) { - return false; - } - - // Fetch the coursemodule. - if (!$cm) { - if (!$cm = get_coursemodule_from_instance('moodleoverflow', $moodleoverflow->id, $moodleoverflow->course)) { - throw new moodle_exception('invalidcoursemodule'); - } - } - - // Fetch the related course. - if (!$course) { - if (!$course = $DB->get_record('course', array('id' => $moodleoverflow->course))) { - throw new moodle_exception('invalidcourseid'); - } - } - - // Fetch the related modulecontext. - if (!$modulecontext) { - $modulecontext = context_module::instance($cm->id); - } - - // Users with temporary guest access can not post. - if (!is_viewing($modulecontext, $user->id) AND !is_enrolled($modulecontext, $user->id, '', true)) { - return false; + if (empty($userid)) { + $userid = $USER->id; } // Check the users capability. - if (has_capability('mod/moodleoverflow:replypost', $modulecontext, $user->id)) { - return true; + if (!has_capability('mod/moodleoverflow:replypost', $modulecontext, $userid)) { + return false; } - - // The user does not have the capability. - return false; + return !$considerreviewstatus || $posttoreplyto->reviewed == 1; } /** @@ -808,10 +839,9 @@ function moodleoverflow_user_can_post($moodleoverflow, $user = null, $cm = null, * @param stdClass $moodleoverflow The moodleoverflow object * @param stdClass $discussion The discussion object * @param stdClass $post The post object - * @param boolean $canreply Whether the user can reply in this discussion */ -function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discussion, $post, $canreply) { - global $USER, $OUTPUT; +function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discussion, $post) { + global $USER; // Check if the current is the starter of the discussion. $ownpost = (isloggedin() AND ($USER->id == $post->userid)); @@ -823,7 +853,7 @@ function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discuss $istracked = \mod_moodleoverflow\readtracking::moodleoverflow_is_tracked($moodleoverflow); // Retrieve all posts of the discussion. - $posts = moodleoverflow_get_all_discussion_posts($discussion->id, $istracked); + $posts = moodleoverflow_get_all_discussion_posts($discussion->id, $istracked, $modulecontext); $usermapping = anonymous::get_userid_mapping($moodleoverflow, $discussion->id); @@ -864,7 +894,7 @@ function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discuss // Print the starting post. echo moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $course, - $ownpost, $canreply, false, '', '', $postread, true, $istracked, 0, $usermapping); + $ownpost, false, '', '', $postread, true, $istracked, 0, $usermapping); // Print answer divider. if ($answercount == 1) { @@ -874,8 +904,12 @@ function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discuss } echo "

$answerstring

"; + echo '
'; + // Print the other posts. - echo moodleoverflow_print_posts_nested($course, $cm, $moodleoverflow, $discussion, $post, $canreply, $istracked, $posts, null, $usermapping); + echo moodleoverflow_print_posts_nested($course, $cm, $moodleoverflow, $discussion, $post, $istracked, $posts, null, $usermapping); + + echo '
'; } /** @@ -883,10 +917,11 @@ function moodleoverflow_print_discussion($course, $cm, $moodleoverflow, $discuss * * @param int $discussionid The ID of the discussion * @param boolean $tracking Whether tracking is activated + * @param context_module $modcontext Context of the module * * @return array */ -function moodleoverflow_get_all_discussion_posts($discussionid, $tracking) { +function moodleoverflow_get_all_discussion_posts($discussionid, $tracking, $modcontext) { global $DB, $USER, $CFG; // Initiate tracking settings. @@ -909,6 +944,13 @@ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking) { $allnames = get_all_user_name_fields(true, 'u'); } + $additionalwhere = ''; + + if (!has_capability('mod/moodleoverflow:reviewpost', $modcontext)) { + $additionalwhere = ' AND (p.reviewed = 1 OR p.userid = :userid2) '; + $params['userid2'] = $USER->id; + } + // Create the sql array. $sql = "SELECT p.*, m.ratingpreference, $allnames, d.name as subject, u.email, u.picture, u.imagealt $trackingselector FROM {moodleoverflow_posts} p @@ -916,7 +958,7 @@ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking) { LEFT JOIN {moodleoverflow_discussions} d ON d.id = p.discussion LEFT JOIN {moodleoverflow} m on m.id = d.moodleoverflow $trackingjoin - WHERE p.discussion = :discussion + WHERE p.discussion = :discussion $additionalwhere ORDER BY p.created ASC"; $params['discussion'] = $discussionid; @@ -984,7 +1026,6 @@ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking) { * @param object $cm * @param stdClass $course The course object * @param bool $ownpost Whether the post was submitted by this user - * @param bool $canreply Whether the user can reply to the post * @param bool $link Whether there is a link to this post * @param string $footer A default footer for posts * @param string $highlight A word to highlight in the post @@ -997,7 +1038,7 @@ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking) { * @return null The output */ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $course, - $ownpost = false, $canreply = false, $link = false, + $ownpost = false, $link = false, $footer = '', $highlight = '', $postisread = null, $dummyifcantsee = true, $istracked = false, $iscomment = false, $usermapping = [], $level = 0) { @@ -1041,6 +1082,7 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $cm->cache->caps['mod/moodleoverflow:viewanyrating'] = has_capability('mod/moodleoverflow:viewanyrating', $modulecontext); $cm->cache->caps['moodle/site:viewfullnames'] = has_capability('moodle/site:viewfullnames', $modulecontext); $cm->cache->caps['mod/moodleoverflow:marksolved'] = has_capability('mod/moodleoverflow:marksolved', $modulecontext); + $cm->cache->caps['mod/moodleoverflow:reviewpost'] = has_capability('mod/moodleoverflow:reviewpost', $modulecontext); } // Check if the user has the capability to see posts. @@ -1123,10 +1165,10 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $link = '/mod/moodleoverflow/discussion.php'; if ($post->statusstarter) { $commands[] = html_writer::tag('a', $str->marknothelpful, - array('class' => 'markhelpful', 'role' => 'button', 'tabindex' => '0')); + array('class' => 'markhelpful onlyifreviewed', 'role' => 'button', 'tabindex' => '0')); } else { $commands[] = html_writer::tag('a', $str->markhelpful, - array('class' => 'markhelpful', 'role' => 'button', 'tabindex' => '0')); + array('class' => 'markhelpful onlyifreviewed', 'role' => 'button', 'tabindex' => '0')); } } @@ -1139,18 +1181,19 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $link = '/mod/moodleoverflow/discussion.php'; if ($post->statusteacher) { $commands[] = html_writer::tag('a', $str->marknotsolved, - array('class' => 'marksolved', 'role' => 'button', 'tabindex' => '0')); + array('class' => 'marksolved onlyifreviewed', 'role' => 'button', 'tabindex' => '0')); } else { $commands[] = html_writer::tag('a', $str->marksolved, - array('class' => 'marksolved', 'role' => 'button', 'tabindex' => '0')); + array('class' => 'marksolved onlyifreviewed', 'role' => 'button', 'tabindex' => '0')); } } // Calculate the age of the post. $age = time() - $post->created; - // Make a link to edit your own post within the given time. - if (($ownpost AND ($age < get_config('moodleoverflow', 'maxeditingtime'))) + // Make a link to edit your own post within the given time and not already reviewed. + if (($ownpost AND ($age < get_config('moodleoverflow', 'maxeditingtime')) && + (!review::should_post_be_reviewed($post, $moodleoverflow) || !$post->reviewed)) OR $cm->cache->caps['mod/moodleoverflow:editanypost'] ) { $editurl = new moodle_url('/mod/moodleoverflow/post.php', array('edit' => $post->id)); @@ -1167,22 +1210,26 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co } // Give the option to reply to a post. - if ($canreply) { + 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', array('reply' => $post->id)); - $commands[] = array('url' => $replyurl, 'text' => $str->replyfirst); + $commands[] = array('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', array('reply' => $post->id)); - $commands[] = array('url' => $replyurl, 'text' => $str->reply); + $commands[] = array('url' => $replyurl, 'text' => $str->reply, 'attributes' => $attributes); // Else simple respond to the answer. } else { $replyurl = new moodle_url('/mod/moodleoverflow/post.php#mformmoodleoverflow', array('reply' => $iscomment)); - $commands[] = array('url' => $replyurl, 'text' => $str->reply); + $commands[] = array('url' => $replyurl, 'text' => $str->reply, 'attributes' => $attributes); } } @@ -1317,6 +1364,13 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $options->trusted = false; $options->context = $modulecontext; + $reviewdelay = get_config('moodleoverflow', 'reviewpossibleaftertime'); + $mustachedata->reviewdelay = format_time($reviewdelay); + $mustachedata->needsreview = !$post->reviewed; + $reviewable = time() - $post->created > $reviewdelay; + $mustachedata->canreview = $cm->cache->caps['mod/moodleoverflow:reviewpost']; + $mustachedata->withinreviewperiod = $reviewable; + // Prepare the post. $mustachedata->postcontent = format_text($post->message, $post->messageformat, $options, $course->id); @@ -1327,12 +1381,12 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co $commandhtml = array(); foreach ($commands as $command) { if (is_array($command)) { - $commandhtml[] = html_writer::link($command['url'], $command['text']); + $commandhtml[] = html_writer::link($command['url'], $command['text'], $command['attributes'] ?? null); } else { $commandhtml[] = $command; } } - $mustachedata->commands = implode(' | ', $commandhtml); + $mustachedata->commands = implode('', $commandhtml); // Print a footer if requested. $mustachedata->footer = $footer; @@ -1365,7 +1419,6 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co * @param object $moodleoverflow The moodleoverflow object * @param object $discussion The discussion object * @param object $parent The object of the parent post - * @param bool $canreply Whether the user has capabilities to reply * @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 @@ -1373,7 +1426,7 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co * @return string The html output. */ function moodleoverflow_print_posts_nested($course, &$cm, $moodleoverflow, $discussion, $parent, - $canreply, $istracked, $posts, $iscomment = null, $usermapping = []) { + $istracked, $posts, $iscomment = null, $usermapping = []) { global $USER; // Prepare the output. @@ -1415,11 +1468,11 @@ function moodleoverflow_print_posts_nested($course, &$cm, $moodleoverflow, $disc // Print the answer. $output .= moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $course, - $ownpost, $canreply, false, '', '', $postread, true, $istracked, $parentid, $usermapping, $level); + $ownpost, false, '', '', $postread, true, $istracked, $parentid, $usermapping, $level); // Print its children. $output .= moodleoverflow_print_posts_nested($course, $cm, $moodleoverflow, - $discussion, $post, $canreply, $istracked, $posts, $parentid, $usermapping); + $discussion, $post, $istracked, $posts, $parentid, $usermapping); // End the div. $output .= "\n"; @@ -1516,6 +1569,7 @@ function moodleoverflow_add_attachment($post, $forum, $cm) { * Adds a new post in an existing discussion. * * @param object $post The post object + * @param context_module $modulecontext * * @return bool|int The Id of the post if operation was successful */ @@ -1535,14 +1589,24 @@ function moodleoverflow_add_new_post($post) { $post->totalscore = 0; } + // Set to not reviewed, if posts should be reviewed, and user is not a reviewer themselves. + if (review::get_review_level($moodleoverflow) == review::EVERYTHING && + !has_capability('mod/moodleoverflow:reviewpost', context_module::instance($cm->id))) { + $post->reviewed = 0; + } else { + $post->reviewed = 1; + } + // Add the post to the database. $post->id = $DB->insert_record('moodleoverflow_posts', $post); - $DB->set_field('moodleoverflow_posts', 'message', $post->message, array('id' => $post->id)); + $DB->set_field('moodleoverflow_posts', 'message', $post->message, array('id' => $post->id)); // ?? moodleoverflow_add_attachment($post, $moodleoverflow, $cm); - // Update the discussion. - $DB->set_field('moodleoverflow_discussions', 'timemodified', $post->modified, array('id' => $post->discussion)); - $DB->set_field('moodleoverflow_discussions', 'usermodified', $post->userid, array('id' => $post->discussion)); + if ($post->reviewed) { + // Update the discussion. + $DB->set_field('moodleoverflow_discussions', 'timemodified', $post->modified, array('id' => $post->discussion)); + $DB->set_field('moodleoverflow_discussions', 'usermodified', $post->userid, array('id' => $post->discussion)); + } // Mark the created post as read if the user is tracking the discussion. $cantrack = \mod_moodleoverflow\readtracking::moodleoverflow_can_track_moodleoverflows($moodleoverflow); @@ -1585,10 +1649,12 @@ function moodleoverflow_update_post($newpost) { } } - // Update the date and the user of the post and the discussion. $post->modified = time(); - $discussion->timemodified = $post->modified; - $discussion->usermodified = $post->userid; + 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) { @@ -1616,35 +1682,22 @@ function moodleoverflow_update_post($newpost) { /** * Count all replies of a post. * - * @param object $post The post object - * @param bool $recursive Whether the deletion should be recursive + * @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, $recursive = null) { +function moodleoverflow_count_replies($post, $onlyreviewed) { global $DB; - // Initiate the variable. - $count = 0; + $conditions = ['parent' => $post->id]; - // Count the posts recursively? - if (isset($recursive)) { - // Get all the direct children. - if ($childposts = $DB->get_records('moodleoverflow_posts', array('parent' => $post->id))) { - - // And count their children as well. - foreach ($childposts as $childpost) { - $count++; - $count += moodleoverflow_count_replies($childpost, true); - } - } - } else { - // Just count the direct children. - $count += $DB->count_records('moodleoverflow_posts', array('parent' => $post->id)); + if ($onlyreviewed) { + $conditions['reviewed'] = '1'; } // Return the amount of replies. - return $count; + return $DB->count_records('moodleoverflow_posts', $conditions); } /** @@ -1670,7 +1723,7 @@ function moodleoverflow_delete_discussion($discussion, $course, $cm, $moodleover foreach ($posts as $post) { $post->course = $discussion->course; $post->moodleoverflow = $discussion->moodleoverflow; - if (!moodleoverflow_delete_post($post, 'ignore', $course, $cm, $moodleoverflow)) { + if (!moodleoverflow_delete_post($post, 'ignore', $cm, $moodleoverflow)) { // If the deletion failed, change the pointer. $result = false; @@ -1694,28 +1747,21 @@ function moodleoverflow_delete_discussion($discussion, $course, $cm, $moodleover /** * Deletes a single moodleoverflow post. * - * @param int $post The post ID - * @param array $children The child posts - * @param object $course The course object. + * @param object $post The post ID + * @param bool $deletechildren The child posts * @param object $cm The course module - * @param int $moodleoverflow The moodleoverflow ID + * @param object $moodleoverflow The moodleoverflow ID * * @return bool Whether the deletion was successful */ -function moodleoverflow_delete_post($post, $children, $course, $cm, $moodleoverflow) { +function moodleoverflow_delete_post($post, $deletechildren, $cm, $moodleoverflow) { global $DB, $USER; // Iterate through all children and delete them. $childposts = $DB->get_records('moodleoverflow_posts', array('parent' => $post->id)); - if (($children !== 'ignore') AND $childposts) { - if ($children) { - foreach ($childposts as $childpost) { - moodleoverflow_delete_post($childpost, true, $course, $cm, $moodleoverflow); - } - - } else { - // If there are no children, return false. - return false; + if ($deletechildren AND $childposts) { + foreach ($childposts as $childpost) { + moodleoverflow_delete_post($childpost, true, $cm, $moodleoverflow); } } @@ -1773,10 +1819,11 @@ function moodleoverflow_discussion_update_last_post($discussionid) { return false; } - // Find the last post of the discussion. + // Find the last reviewed post of the discussion. (even if user has review capability, because it is written to DB) $sql = "SELECT id, userid, modified FROM {moodleoverflow_posts} WHERE discussion = ? + AND reviewed = 1 ORDER BY modified DESC"; // Find the new last post of the discussion. diff --git a/markposts.php b/markposts.php index 00f7889d7fb..9c1ba8b1d1e 100644 --- a/markposts.php +++ b/markposts.php @@ -106,7 +106,7 @@ } // Mark all the discussions read. - if (!\mod_moodleoverflow\readtracking::moodleoverflow_mark_discussion_read($discussionid, $user->id)) { + if (!\mod_moodleoverflow\readtracking::moodleoverflow_mark_discussion_read($discussionid, context_module::instance($cm->id), $user->id)) { // Display an error, if something failes. $message = get_string('markreadfailed', 'moodleoverflow'); diff --git a/mod_form.php b/mod_form.php index 94107763161..a49187c1109 100644 --- a/mod_form.php +++ b/mod_form.php @@ -26,6 +26,7 @@ */ use mod_moodleoverflow\anonymous; +use mod_moodleoverflow\review; defined('MOODLE_INTERNAL') || die(); @@ -88,6 +89,18 @@ public function definition() { $mform->setDefault('anonymous', anonymous::NOT_ANONYMOUS); } + if (get_config('moodleoverflow', 'allowreview') == 1) { + $possiblesettings = [ + review::NOTHING => get_string('nothing', 'moodleoverflow'), + review::QUESTIONS => get_string('questions', 'moodleoverflow'), + review::EVERYTHING => get_string('questions_and_posts', 'moodleoverflow') + ]; + + $mform->addElement('select', 'needsreview', get_string('review', 'moodleoverflow'), $possiblesettings); + $mform->addHelpButton('needsreview', 'review', 'moodleoverflow'); + $mform->setDefault('needsreview', review::NOTHING); + } + // Attachments. $mform->addElement('header', 'attachmentshdr', get_string('attachments', 'moodleoverflow')); diff --git a/post.php b/post.php index 606c5b45d1c..530789f4f6c 100644 --- a/post.php +++ b/post.php @@ -22,8 +22,13 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +// TODO refactor this. For more readability, and to avoid security issues. + // Include config and locallib. +use mod_moodleoverflow\review; + require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); +global $CFG, $USER, $DB, $PAGE, $SESSION, $OUTPUT; require_once(dirname(__FILE__) . '/locallib.php'); require_once($CFG->libdir . '/completionlib.php'); @@ -213,7 +218,7 @@ $coursecontext = context_course::instance($course->id); // Check whether the user is allowed to post. - if (!moodleoverflow_user_can_post($moodleoverflow, $USER, $cm, $course, $modulecontext)) { + if (!moodleoverflow_user_can_post($modulecontext, $parent)) { // Give the user the chance to enroll himself to the course. if (!isguestuser() AND !is_enrolled($coursecontext)) { @@ -295,12 +300,15 @@ $PAGE->set_cm($cm, $course, $moodleoverflow); // Check if the post can be edited. - $intime = ((time() - $post->created) > get_config('moodleoverflow', 'maxeditingtime')); - if ($intime AND !has_capability('mod/moodleoverflow:editanypost', $modulecontext)) { + $beyondtime = ((time() - $post->created) > get_config('moodleoverflow', 'maxeditingtime')); + $alreadyreviewed = review::should_post_be_reviewed($post, $moodleoverflow) && $post->reviewed; + if (($beyondtime || $alreadyreviewed) AND !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) { @@ -360,7 +368,7 @@ } // Count all replies of this post. - $replycount = moodleoverflow_count_replies($post); + $replycount = moodleoverflow_count_replies($post, false); // Has the user confirmed the deletion? if (!empty($confirm) AND confirm_sesskey()) { @@ -396,7 +404,7 @@ redirect("view.php?m=$discussion->moodleoverflow"); exit; - } else if (moodleoverflow_delete_post($post, $deleteanypost, $course, $cm, $moodleoverflow)) { + } 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', array('d' => $discussion->id)); @@ -527,6 +535,7 @@ $postmessage = empty($post->message) ? null : $post->message; // Set data for the form. +// TODO Refactor. $param1 = (isset($discussion->id) ? array($discussion->id) : array()); $param2 = (isset($post->format) ? array('format' => $post->format) : array()); $param3 = (isset($discussion->timestart) ? array('timestart' => $discussion->timestart) : array()); @@ -598,7 +607,7 @@ $replypost = has_capability('mod/moodleoverflow:replypost', $modulecontext); $startdiscussion = has_capability('mod/moodleoverflow:startdiscussion', $modulecontext); $ownpost = ($realpost->userid == $USER->id); - if (!((($ownpost AND $replypost OR $startdiscussion)) OR $editanypost)) { + if (!(($ownpost AND ($replypost OR $startdiscussion)) OR $editanypost)) { throw new moodle_exception('cannotupdatepost', 'moodleoverflow'); } diff --git a/renderer.php b/renderer.php index 7ce983ebb5d..6790e97726b 100644 --- a/renderer.php +++ b/renderer.php @@ -66,7 +66,7 @@ public function render_post_dummy_cantsee($data) { * @return bool|string */ public function render_question($data) { - return $this->render_from_template('mod_moodleoverflow/question', $data); + return $this->render_from_template('mod_moodleoverflow/answer', $data); } /** diff --git a/settings.php b/settings.php index 09957e082f7..17f0359ee6e 100644 --- a/settings.php +++ b/settings.php @@ -93,6 +93,13 @@ $settings->add(new admin_setting_configcheckbox('moodleoverflow/allowratingchange', get_string('allowratingchange', 'moodleoverflow'), get_string('configallowratingchange', 'moodleoverflow'), 1)); + // Allow teachers to enable review before publish. + $settings->add(new admin_setting_configcheckbox('moodleoverflow/allowreview', + get_string('allowreview', 'moodleoverflow'), get_string('allowreview_desc', 'moodleoverflow'), 1)); + + $settings->add(new admin_setting_configtext('moodleoverflow/reviewpossibleaftertime', get_string('reviewpossibleaftertime', 'moodleoverflow'), + get_string('reviewpossibleaftertime_desc', 'moodleoverflow'), 3600, PARAM_INT)); + // Set scales for the reputation. $votesettings = []; diff --git a/styles.css b/styles.css index 9f517a65910..9127790d098 100644 --- a/styles.css +++ b/styles.css @@ -129,77 +129,17 @@ * The styles for the discussion.php */ .moodleoverflowpost { - border: 1px solid #000; - display: block; margin: 0 0 1em 0; max-width: 100%; - padding: 0; - position: relative; -} - -.moodleoverflowpost .row { position: relative; - width: 100%; -} - -.moodleoverflowpost .row .left { - float: left; - overflow: hidden; - width: 43px; -} - -.moodleoverflowpost .row .left + .no-overflow { - max-width: calc(100% - 43px); -} - -.moodleoverflowpost .row .left .votes { - display: block; - margin: 0 8px 0 0; - text-align: center; -} - -.moodleoverflowpost .row .left .votes p { - margin-bottom: 0; - margin-top: 0; -} - -.moodleoverflowpost .row .left .votes .icon { - color: #0a0; - height: 24px; - margin: 0; - width: 24px; -} - -.moodleoverflowpost .row .topic, -.moodleoverflowpost .row .content-mask, -.moodleoverflowpost .row .options { - margin-left: 43px; + border-radius: 0; + display: flex; } .moodleoverflowpost .picture img { margin: 4px; } -.moodleoverflowpost .options .commands, -.moodleoverflowpost .content .attachments, -.moodleoverflowpost .options .footer, -.moodleoverflowpost .options .link { - text-align: right; -} - -.moodleoverflowpost .options .moodleoverflow-post-rating { - float: left; -} - -.moodleoverflowpost .content .posting { - max-width: 100%; - overflow: auto; -} - -.moodleoverflowpost .content .attachedimages img { - max-width: 100%; -} - .moodleoverflowpost .post-word-count { font-size: .85em; font-style: italic; @@ -210,7 +150,7 @@ padding: 0 .3em; } -:target ~ .moodleoverflowpost:before { +.moodleoverflowpost:target:before { background: #0070a8; bottom: -1px; content: ""; @@ -221,14 +161,6 @@ width: 4px; } -.moodleoverflowpost { - border-radius: 0; - display: flex; - margin-bottom: 5px; - position: relative; - padding: 6px; -} - .moodleoverflowpost.statusboth:before { background: linear-gradient(80deg, rgba(234, 133, 22, 1) 50%, rgba(112, 160, 52, 1) 50%); bottom: -1px; @@ -296,45 +228,19 @@ margin-right: 10px; } -.moodleoverflowpost .content .posting.fullpost { - margin-top: 8px; -} - -.moodleoverflowpost .row.side { - clear: both; -} - -.moodleoverflowpost .options .commands { - margin-left: 0; -} - -.moodleoverflowpost .options .moodleoverflow-post-rating .icon { - height: 20px; - width: 24px; -} - .moodleoverflowpost .subject { font-weight: 700; } .moodleoverflowpost.unread .row.header, -span.unread { - background-color: #ffd; +.mod_moodleoverflow-label-unread { + background-color: #fff38a; } .moodleoverflowpost.unread .row.header { border-bottom: 1px solid #ddd; } -.moodleoverflowpost .row { - margin-left: 0; -} - -.moodleoverflowpost .row:before, -.moodleoverflowpost .row:after { - content: none; -} - .moodleoverflowpost .topic .author { color: #848d95; } @@ -391,6 +297,12 @@ span.unread { color: grey; } +.moodleoverflowpost .post-menu > :not(:last-child) { + border-right: 2px solid #bbb; + padding-right: 4px; + margin-right: 4px; +} + /* * The Answers. */ @@ -531,8 +443,41 @@ span.unread { border-color: #900000; } +.moodleoverflowpost.pendingreview > :first-child { + opacity: 0.6; + transition: opacity 200ms linear; +} + +.moodleoverflowpost.pendingreview:hover > :first-child { + opacity: 1; +} + +.moodleoverflowpost.pendingreview .onlyifreviewed { + display: none; +} + +.moodleoverflowpost:not(.pendingreview) .onlyifnotreviewed { + display: none; +} + @media (max-width: 600px) { .hide-600 { display: none; } +} + +.icon.moodleoverflow-icon-big { + font-size: 2em; + width: 32px; + height: 32px; +} + +.mod_moodleoverflow-label-review, +.mod_moodleoverflow-label-unread { + padding: 4px; + border-radius: 8px; +} + +.mod_moodleoverflow-label-review { + background-color: #ffd3d3; } \ No newline at end of file diff --git a/templates/answer.mustache b/templates/answer.mustache index 193ac4ea624..f56562fcc56 100644 --- a/templates/answer.mustache +++ b/templates/answer.mustache @@ -30,93 +30,118 @@ {{/ isfirstunread}} -{{! Print the anchor to the post. }} - - {{! Start the post. Mark it read or unread. }} -
- {{#showvotes}} -
-
- {{> mod_moodleoverflow/postvoting }} +
+
+ {{#showvotes}} +
+
+ {{> mod_moodleoverflow/postvoting }} +
-
- {{/showvotes}} - {{^showvotes}} -
- {{/showvotes}} -
-
- {{{ postcontent }}} -
-
- {{#attachments}} - {{#image}} - + {{/showvotes}} + {{^showvotes}} +
+ {{/showvotes}} + {{#needsreview}} +
+ {{#pix}}i/pending-big, mod_moodleoverflow, + {{#withinreviewperiod}} + {{#str}}pending_review, mod_moodleoverflow{{/str}} + {{/withinreviewperiod}} + {{^withinreviewperiod}} + {{#str}}pending_review_but_cannot_now, mod_moodleoverflow, {{reviewdelay}} {{/str}} + {{/withinreviewperiod}} + {{/pix}} +
+ {{/needsreview}} +
+
+ {{{ postcontent }}} +
+
+ {{#attachments}} + {{#image}} + +
+ {{/image}} + {{^image}} + + {{{icon}}} + + + {{filename}} + + {{/image}}
- {{/image}} - {{^image}} - - {{{icon}}} - - - {{filename}} - - {{/image}} -
- {{/attachments}} -
-