From 5dd892bd010e126b2c8f13a47460144c3a77bdcf Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 7 Dec 2016 12:07:22 -0500 Subject: [PATCH] a bunch of changes here... allowing user profiles to be flagged, #5232 --- public/language/en-GB/flags.json | 13 +++- public/language/en-GB/notifications.json | 3 + public/language/en-GB/topic.json | 9 +-- public/language/en-GB/user.json | 1 + public/src/client/account/header.js | 10 +++ public/src/client/flags/list.js | 2 + public/src/client/topic/postTools.js | 9 ++- .../topic/flag.js => modules/flags.js} | 25 +++--- src/controllers/mods.js | 6 +- src/flags.js | 76 +++++++++++++++++-- src/meta/js.js | 4 +- src/notifications.js | 2 + test/flags.js | 12 ++- 13 files changed, 133 insertions(+), 39 deletions(-) rename public/src/{client/topic/flag.js => modules/flags.js} (62%) diff --git a/public/language/en-GB/flags.json b/public/language/en-GB/flags.json index 6b4e96f4fb..a9272897ca 100644 --- a/public/language/en-GB/flags.json +++ b/public/language/en-GB/flags.json @@ -29,6 +29,9 @@ "start-new-chat": "Start New Chat", "go-to-target": "View Flag Target", + "user-view": "View Profile", + "user-edit": "Edit Profile", + "notes": "Flag Notes", "add-note": "Add Note", "no-notes": "No shared notes.", @@ -43,5 +46,13 @@ "state-resolved": "Resolved", "state-rejected": "Rejected", "no-assignee": "Not Assigned", - "note-added": "Note Added" + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." } \ No newline at end of file diff --git a/public/language/en-GB/notifications.json b/public/language/en-GB/notifications.json index 5a2ed58908..0838ca17eb 100644 --- a/public/language/en-GB/notifications.json +++ b/public/language/en-GB/notifications.json @@ -21,6 +21,9 @@ "user_flagged_post_in": "%1 flagged a post in %2", "user_flagged_post_in_dual": "%1 and %2 flagged a post in %3", "user_flagged_post_in_multiple": "%1 and %2 others flagged a post in %3", + "user_flagged_user": "%1 flagged a user profile (%2)", + "user_flagged_user_dual": "%1 and %2 flagged a user profile (%3)", + "user_flagged_user_multiple": "%1 and %2 others flagged a user profile (%3)", "user_posted_to" : "%1 has posted a reply to: %2", "user_posted_to_dual" : "%1 and %2 have posted replies to: %3", "user_posted_to_multiple" : "%1 and %2 others have posted replies to: %3", diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json index 4ae208076e..5571292dda 100644 --- a/public/language/en-GB/topic.json +++ b/public/language/en-GB/topic.json @@ -28,7 +28,6 @@ "link": "Link", "share": "Share", "tools": "Tools", - "flag": "Flag", "locked": "Locked", "pinned": "Pinned", "moved": "Moved", @@ -36,7 +35,6 @@ "bookmark_instructions" : "Click here to return to the last read post in this thread.", "flag_title": "Flag this post for moderation", - "flag_success": "This post has been flagged for moderation.", "deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.", @@ -138,10 +136,5 @@ "stale.create": "Create a new topic", "stale.reply_anyway": "Reply to this topic anyway", - "link_back": "Re: [%1](%2)\n\n", - - "spam": "Spam", - "offensive": "Offensive", - "custom-flag-reason": "Enter a flagging reason" - + "link_back": "Re: [%1](%2)\n\n" } diff --git a/public/language/en-GB/user.json b/public/language/en-GB/user.json index f0cb35f615..0725f208a3 100644 --- a/public/language/en-GB/user.json +++ b/public/language/en-GB/user.json @@ -35,6 +35,7 @@ "chat": "Chat", "chat_with": "Continue chat with %1", "new_chat_with": "Start new chat with %1", + "flag-profile": "Flag Profile", "follow": "Follow", "unfollow": "Unfollow", "more": "More", diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index d225e2cae1..b53cbb2be4 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -49,6 +49,7 @@ define('forum/account/header', [ components.get('account/ban').on('click', banAccount); components.get('account/unban').on('click', unbanAccount); components.get('account/delete').on('click', deleteAccount); + components.get('account/flag').on('click', flagAccount); }; function hidePrivateLinks() { @@ -167,6 +168,15 @@ define('forum/account/header', [ }); } + function flagAccount() { + require(['flags'], function (flags) { + flags.showFlagModal({ + type: 'user', + id: ajaxify.data.uid + }); + }); + } + function removeCover() { socket.emit('user.removeCover', { uid: ajaxify.data.uid diff --git a/public/src/client/flags/list.js b/public/src/client/flags/list.js index cbdaf94f24..bf8b4bc1a8 100644 --- a/public/src/client/flags/list.js +++ b/public/src/client/flags/list.js @@ -23,6 +23,8 @@ define('forum/flags/list', ['components'], function (components) { var qs = payload.map(function (filter) { if (filter.value) { return filter.name + '=' + filter.value; + } else { + return; } }).filter(Boolean).join('&'); diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index faa0567878..5618e692db 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -167,10 +167,11 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator postContainer.on('click', '[component="post/flag"]', function () { var pid = getData($(this), 'data-pid'); - var username = getData($(this), 'data-username'); - var userslug = getData($(this), 'data-userslug'); - require(['forum/topic/flag'], function (flag) { - flag.showFlagModal(pid, username, userslug); + require(['flags'], function (flags) { + flags.showFlagModal({ + type: 'post', + id: pid + }); }); }); diff --git a/public/src/client/topic/flag.js b/public/src/modules/flags.js similarity index 62% rename from public/src/client/topic/flag.js rename to public/src/modules/flags.js index 6b3440da54..cc9fd5103a 100644 --- a/public/src/client/topic/flag.js +++ b/public/src/modules/flags.js @@ -2,18 +2,13 @@ /* globals define, app, socket, templates */ -define('forum/topic/flag', [], function () { - +define('flags', [], function () { var Flag = {}, flagModal, flagCommit; - Flag.showFlagModal = function (pid, username, userslug) { - parseModal({ - pid: pid, - username: username, - userslug: userslug - }, function (html) { + Flag.showFlagModal = function (data) { + parseModal(data, function (html) { flagModal = $(html); flagModal.on('hidden.bs.modal', function () { @@ -23,11 +18,11 @@ define('forum/topic/flag', [], function () { flagCommit = flagModal.find('#flag-post-commit'); flagModal.on('click', '.flag-reason', function () { - flagPost(pid, $(this).text()); + createFlag(data.type, data.id, $(this).text()); }); flagCommit.on('click', function () { - flagPost(pid, flagModal.find('#flag-reason-custom').val()); + createFlag(data.type, data.id, flagModal.find('#flag-reason-custom').val()); }); flagModal.modal('show'); @@ -37,24 +32,24 @@ define('forum/topic/flag', [], function () { }; function parseModal(tplData, callback) { - templates.parse('partials/modals/flag_post_modal', tplData, function (html) { + templates.parse('partials/modals/flag_modal', tplData, function (html) { require(['translator'], function (translator) { translator.translate(html, callback); }); }); } - function flagPost(pid, reason) { - if (!pid || !reason) { + function createFlag(type, id, reason) { + if (!type || !id || !reason) { return; } - socket.emit('flags.create', {type: 'post', id: pid, reason: reason}, function (err) { + socket.emit('flags.create', {type: type, id: id, reason: reason}, function (err) { if (err) { return app.alertError(err.message); } flagModal.modal('hide'); - app.alertSuccess('[[topic:flag_success]]'); + app.alertSuccess('[[flags:modal-submit-success]]'); }); } diff --git a/src/controllers/mods.js b/src/controllers/mods.js index 788294a01e..656605f277 100644 --- a/src/controllers/mods.js +++ b/src/controllers/mods.js @@ -63,7 +63,11 @@ modsController.flags.detail = function (req, res, next) { } res.render('flags/detail', Object.assign(results.flagData, { - assignees: results.assignees + assignees: results.assignees, + type_bool: ['post', 'user'].reduce(function (memo, cur) { + memo[cur] = results.flagData.type === cur; + return memo; + }, {}) })); }); }; diff --git a/src/flags.js b/src/flags.js index 45aae2502e..9f121f81bf 100644 --- a/src/flags.js +++ b/src/flags.js @@ -167,20 +167,44 @@ Flags.validate = function (payload, callback) { switch (payload.type) { case 'post': async.parallel({ - privileges: async.apply(privileges.posts.get, [payload.id], payload.uid) + editable: async.apply(privileges.posts.canEdit, payload.id, payload.uid) }, function (err, subdata) { if (err) { return callback(err); } var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1; - if (!subdata.privileges[0].isAdminOrMod && parseInt(data.reporter.reputation, 10) < minimumReputation) { + // Check if reporter meets rep threshold (or can edit the target post, in which case threshold does not apply) + if (!subdata.editable.flag && parseInt(data.reporter.reputation, 10) < minimumReputation) { return callback(new Error('[[error:not-enough-reputation-to-flag]]')); } callback(); }); break; + + case 'user': + async.parallel({ + editable: async.apply(privileges.users.canEdit, payload.uid, payload.id) + }, function (err, subdata) { + if (err) { + return callback(err); + } + + + var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1; + // Check if reporter meets rep threshold (or can edit the target user, in which case threshold does not apply) + if (!subdata.editable && parseInt(data.reporter.reputation, 10) < minimumReputation) { + return callback(new Error('[[error:not-enough-reputation-to-flag]]')); + } + + callback(); + }); + break; + + default: + callback(new Error('[[error:invalid-data]]')); + break; } }); }; @@ -369,13 +393,17 @@ Flags.exists = function (type, id, uid, callback) { Flags.targetExists = function (type, id, callback) { switch (type) { - case 'topic': // just an example... - topics.exists(id, callback); - break; - case 'post': posts.exists(id, callback); break; + + case 'user': + user.exists(id, callback); + break; + + default: + callback(new Error('[[error:invalid-data]]')); + break; } }; @@ -384,6 +412,10 @@ Flags.getTargetUid = function (type, id, callback) { case 'post': posts.getPostField(id, 'uid', callback); break; + + case 'user': + setImmediate(callback, null, id); + break; } }; @@ -553,7 +585,7 @@ Flags.notify = function (flagObj, uid, callback) { notifications.create({ bodyShort: '[[notifications:user_flagged_post_in, ' + flagObj.reporter.username + ', ' + titleEscaped + ']]', - bodyLong: results.post.content, + bodyLong: flagObj.description, pid: flagObj.targetId, path: '/post/' + flagObj.targetId, nid: 'flag:post:' + flagObj.targetId + ':uid:' + uid, @@ -570,6 +602,36 @@ Flags.notify = function (flagObj, uid, callback) { }); }); break; + + case 'user': + async.parallel({ + admins: async.apply(groups.getMembers, 'administrators', 0, -1), + globalMods: async.apply(groups.getMembers, 'Global Moderators', 0, -1), + }, function (err, results) { + if (err) { + return callback(err); + } + + notifications.create({ + bodyShort: '[[notifications:user_flagged_user, ' + flagObj.reporter.username + ', ' + flagObj.target.username + ']]', + bodyLong: flagObj.description, + path: '/uid/' + flagObj.targetId, + nid: 'flag:user:' + flagObj.targetId + ':uid:' + uid, + from: uid, + mergeId: 'notifications:user_flagged_user|' + flagObj.targetId + }, function (err, notification) { + if (err || !notification) { + return callback(err); + } + + notifications.push(notification, results.admins.concat(results.globalMods), callback); + }); + }); + break; + + default: + callback(new Error('[[error:invalid-data]]')); + break; } }; diff --git a/src/meta/js.js b/src/meta/js.js index 626fa0ecd8..947550f37e 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -49,7 +49,6 @@ module.exports = function (Meta) { 'public/src/client/unread.js', 'public/src/client/topic.js', 'public/src/client/topic/events.js', - 'public/src/client/topic/flag.js', 'public/src/client/topic/fork.js', 'public/src/client/topic/move.js', 'public/src/client/topic/posts.js', @@ -72,7 +71,8 @@ module.exports = function (Meta) { 'public/src/modules/taskbar.js', 'public/src/modules/helpers.js', 'public/src/modules/sounds.js', - 'public/src/modules/string.js' + 'public/src/modules/string.js', + 'public/src/modules/flags.js' ], // modules listed below are routed through express (/src/modules) so they can be defined anonymously diff --git a/src/notifications.js b/src/notifications.js index b99700be01..0fb1e1ace3 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -415,6 +415,7 @@ var utils = require('../public/src/utils'); 'notifications:user_started_following_you', 'notifications:user_posted_to', 'notifications:user_flagged_post_in', + 'notifications:user_flagged_user', 'new_register' ], isolated, differentiators, differentiator, modifyIndex, set; @@ -462,6 +463,7 @@ var utils = require('../public/src/utils'); case 'notifications:user_started_following_you': case 'notifications:user_posted_to': case 'notifications:user_flagged_post_in': + case 'notifications:user_flagged_user': var usernames = set.map(function (notifObj) { return notifObj && notifObj.user && notifObj.user.username; }).filter(function (username, idx, array) { diff --git a/test/flags.js b/test/flags.js index ee735afd60..14bb1e65c6 100644 --- a/test/flags.js +++ b/test/flags.js @@ -32,6 +32,11 @@ describe('Flags', function () { content: 'This is flaggable content' }, next); }); + }, + function (topicData, next) { + User.create({ + username: 'testUser2', password: 'abcdef', email: 'c@d.com' + }, next); } ], done); }); @@ -212,7 +217,7 @@ describe('Flags', function () { Flags.validate({ type: 'post', id: 1, - uid: 1 + uid: 2 }, function (err) { assert.ok(err); assert.strictEqual('[[error:not-enough-reputation-to-flag]]', err.message); @@ -305,6 +310,10 @@ describe('Flags', function () { assert.ifError(err); Flags.getHistory(1, function (err, history) { + if (err) { + throw err; + } + assert.strictEqual(entries + 1, history.length); done(); }); @@ -315,6 +324,7 @@ describe('Flags', function () { describe('.getHistory()', function () { it('should retrieve a flag\'s history', function (done) { Flags.getHistory(1, function (err, history) { + assert.ifError(err); assert.strictEqual(history[0].fields[0].value, '[[flags:state-rejected]]'); done(); });