mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 22:43:33 +08:00
FEATURE: flag dispositions normalization
All flags should end up in one of the three dispositions - Agree - Disagree - Defer In the administration area, the *active* flags section displays 4 buttons - Agree (hide post + send PM) - Disagree - Defer - Delete Clicking "Delete" will open a modal that offer to - Delete Post & Defer Flags - Delete Post & Agree with Flags - Delete Spammer (if available) When the flag has a list associated, the list will now display 1 response and 1 reply and a "show more..." link if there are more in the conversation. Replying to the conversation will NOT give a disposition. Moderators must click the buttons that does that. If someone clicks one buttons, this will add a default moderator message from that moderator saying what happened. The *old* flags section now displays the proper dispositions and is super duper fast (no more N+9999 queries). FIX: the old list includes deleted topics FIX: the lists now properly display the topic states (deleted, closed, archived, hidden, PM) FIX: flagging a topic that you've already flagged the first post
This commit is contained in:
@ -8,36 +8,34 @@
|
|||||||
**/
|
**/
|
||||||
export default Ember.ArrayController.extend({
|
export default Ember.ArrayController.extend({
|
||||||
|
|
||||||
|
adminOldFlagsView: Em.computed.equal('query', 'old'),
|
||||||
|
adminActiveFlagsView: Em.computed.equal('query', 'active'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
|
||||||
Clear all flags on a post
|
|
||||||
|
|
||||||
@method clearFlags
|
agreeFlags: function (flaggedPost) {
|
||||||
@param {Discourse.FlaggedPost} item The post whose flags we want to clear
|
var self = this;
|
||||||
**/
|
flaggedPost.agreeFlags().then(function () {
|
||||||
disagreeFlags: function(item) {
|
self.removeObject(flaggedPost);
|
||||||
var adminFlagsController = this;
|
}, function () {
|
||||||
item.disagreeFlags().then(function() {
|
|
||||||
adminFlagsController.removeObject(item);
|
|
||||||
}, function() {
|
|
||||||
bootbox.alert(I18n.t("admin.flags.error"));
|
bootbox.alert(I18n.t("admin.flags.error"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
agreeFlags: function(item) {
|
disagreeFlags: function (flaggedPost) {
|
||||||
var adminFlagsController = this;
|
var self = this;
|
||||||
item.agreeFlags().then(function() {
|
flaggedPost.disagreeFlags().then(function () {
|
||||||
adminFlagsController.removeObject(item);
|
self.removeObject(flaggedPost);
|
||||||
}, function() {
|
}, function () {
|
||||||
bootbox.alert(I18n.t("admin.flags.error"));
|
bootbox.alert(I18n.t("admin.flags.error"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deferFlags: function(item) {
|
deferFlags: function (flaggedPost) {
|
||||||
var adminFlagsController = this;
|
var self = this;
|
||||||
item.deferFlags().then(function() {
|
flaggedPost.deferFlags().then(function () {
|
||||||
adminFlagsController.removeObject(item);
|
self.removeObject(flaggedPost);
|
||||||
}, function() {
|
}, function () {
|
||||||
bootbox.alert(I18n.t("admin.flags.error"));
|
bootbox.alert(I18n.t("admin.flags.error"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -45,47 +43,8 @@ export default Ember.ArrayController.extend({
|
|||||||
doneTopicFlags: function(item) {
|
doneTopicFlags: function(item) {
|
||||||
this.send('disagreeFlags', item);
|
this.send('disagreeFlags', item);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
Deletes a post
|
|
||||||
|
|
||||||
@method deletePost
|
|
||||||
@param {Discourse.FlaggedPost} post The post to delete
|
|
||||||
**/
|
|
||||||
deletePost: function(post) {
|
|
||||||
var adminFlagsController = this;
|
|
||||||
post.deletePost().then(function() {
|
|
||||||
adminFlagsController.removeObject(post);
|
|
||||||
}, function() {
|
|
||||||
bootbox.alert(I18n.t("admin.flags.error"));
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
Deletes a user and all posts and topics created by that user.
|
|
||||||
|
|
||||||
@method deleteSpammer
|
|
||||||
@param {Discourse.FlaggedPost} item The post to delete
|
|
||||||
**/
|
|
||||||
deleteSpammer: function(item) {
|
|
||||||
item.get('user').deleteAsSpammer(function() { window.location.reload(); });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Are we viewing the 'old' view?
|
|
||||||
|
|
||||||
@property adminOldFlagsView
|
|
||||||
**/
|
|
||||||
adminOldFlagsView: Em.computed.equal('query', 'old'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
Are we viewing the 'active' view?
|
|
||||||
|
|
||||||
@property adminActiveFlagsView
|
|
||||||
**/
|
|
||||||
adminActiveFlagsView: Em.computed.equal('query', 'active'),
|
|
||||||
|
|
||||||
loadMore: function(){
|
loadMore: function(){
|
||||||
var flags = this.get('model');
|
var flags = this.get('model');
|
||||||
return Discourse.FlaggedPost.findAll(this.get('query'),flags.length+1).then(function(data){
|
return Discourse.FlaggedPost.findAll(this.get('query'),flags.length+1).then(function(data){
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
The modal for deleting a flag.
|
||||||
|
|
||||||
|
@class AdminDeleteFlagController
|
||||||
|
@extends Discourse.Controller
|
||||||
|
@namespace Discourse
|
||||||
|
@uses Discourse.ModalFunctionality
|
||||||
|
@module Discourse
|
||||||
|
**/
|
||||||
|
Discourse.AdminDeleteFlagController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
||||||
|
|
||||||
|
needs: ["adminFlags"],
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
|
||||||
|
deletePostDeferFlag: function () {
|
||||||
|
var adminFlagController = this.get("controllers.adminFlags");
|
||||||
|
var post = this.get("content");
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return post.deferFlags(true).then(function () {
|
||||||
|
adminFlagController.removeObject(post);
|
||||||
|
self.send("closeModal");
|
||||||
|
}, function () {
|
||||||
|
bootbox.alert(I18n.t("admin.flags.error"));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deletePostAgreeFlag: function () {
|
||||||
|
var adminFlagController = this.get("controllers.adminFlags");
|
||||||
|
var post = this.get("content");
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return post.agreeFlags(true).then(function () {
|
||||||
|
adminFlagController.removeObject(post);
|
||||||
|
self.send("closeModal");
|
||||||
|
}, function () {
|
||||||
|
bootbox.alert(I18n.t("admin.flags.error"));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Deletes a user and all posts and topics created by that user.
|
||||||
|
|
||||||
|
@method deleteSpammer
|
||||||
|
**/
|
||||||
|
deleteSpammer: function () {
|
||||||
|
this.get("content.user").deleteAsSpammer(function() { window.location.reload(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
@ -8,64 +8,69 @@
|
|||||||
**/
|
**/
|
||||||
Discourse.FlaggedPost = Discourse.Post.extend({
|
Discourse.FlaggedPost = Discourse.Post.extend({
|
||||||
|
|
||||||
summary: function(){
|
summary: function () {
|
||||||
return _(this.post_actions)
|
return _(this.post_actions)
|
||||||
.groupBy(function(a){ return a.post_action_type_id; })
|
.groupBy(function (a) { return a.post_action_type_id; })
|
||||||
.map(function(v,k){
|
.map(function (v,k) { return I18n.t('admin.flags.summary.action_type_' + k, { count: v.length }); })
|
||||||
return I18n.t('admin.flags.summary.action_type_' + k, {count: v.length});
|
|
||||||
})
|
|
||||||
.join(',');
|
.join(',');
|
||||||
}.property(),
|
}.property(),
|
||||||
|
|
||||||
flaggers: function() {
|
flaggers: function () {
|
||||||
var r,
|
var self = this;
|
||||||
_this = this;
|
var flaggers = [];
|
||||||
r = [];
|
|
||||||
_.each(this.post_actions, function(action) {
|
|
||||||
var user = _this.userLookup[action.user_id];
|
|
||||||
var deletedBy = null;
|
|
||||||
if(action.deleted_by_id){
|
|
||||||
deletedBy = _this.userLookup[action.deleted_by_id];
|
|
||||||
}
|
|
||||||
|
|
||||||
var flagType = I18n.t('admin.flags.summary.action_type_' + action.post_action_type_id, {count: 1});
|
_.each(this.post_actions, function (postAction) {
|
||||||
|
flaggers.push({
|
||||||
r.push({
|
user: self.userLookup[postAction.user_id],
|
||||||
user: user, flagType: flagType, flaggedAt: action.created_at, deletedBy: deletedBy,
|
topic: self.topicLookup[postAction.topic_id],
|
||||||
tookAction: action.staff_took_action, deletedAt: action.deleted_at
|
flagType: I18n.t('admin.flags.summary.action_type_' + postAction.post_action_type_id, { count: 1 }),
|
||||||
|
flaggedAt: postAction.created_at,
|
||||||
|
disposedBy: postAction.disposed_by_id ? self.userLookup[postAction.disposed_by_id] : null,
|
||||||
|
disposedAt: postAction.disposed_at,
|
||||||
|
disposition: postAction.disposition ? I18n.t('admin.flags.dispositions.' + postAction.disposition) : null,
|
||||||
|
tookAction: postAction.staff_took_action
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return r;
|
|
||||||
|
return flaggers;
|
||||||
}.property(),
|
}.property(),
|
||||||
|
|
||||||
messages: function() {
|
conversations: function () {
|
||||||
var r,
|
var self = this;
|
||||||
_this = this;
|
var conversations = [];
|
||||||
r = [];
|
|
||||||
_.each(this.post_actions,function(action) {
|
_.each(this.post_actions, function (postAction) {
|
||||||
if (action.message) {
|
if (postAction.conversation) {
|
||||||
r.push({
|
var conversation = {
|
||||||
user: _this.userLookup[action.user_id],
|
permalink: postAction.permalink,
|
||||||
message: action.message,
|
hasMore: postAction.conversation.has_more,
|
||||||
permalink: action.permalink,
|
response: {
|
||||||
bySystemUser: (action.user_id === -1 ? true : false)
|
excerpt: postAction.conversation.response.excerpt,
|
||||||
});
|
user: self.userLookup[postAction.conversation.response.user_id]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (postAction.conversation.reply) {
|
||||||
|
conversation["reply"] = {
|
||||||
|
excerpt: postAction.conversation.reply.excerpt,
|
||||||
|
user: self.userLookup[postAction.conversation.reply.user_id]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
conversations.push(conversation);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return r;
|
|
||||||
}.property(),
|
|
||||||
|
|
||||||
lastFlagged: function() {
|
return conversations;
|
||||||
return this.post_actions[0].created_at;
|
|
||||||
}.property(),
|
}.property(),
|
||||||
|
|
||||||
user: function() {
|
user: function() {
|
||||||
return this.userLookup[this.user_id];
|
return this.userLookup[this.user_id];
|
||||||
}.property(),
|
}.property(),
|
||||||
|
|
||||||
topicHidden: function() {
|
topic: function () {
|
||||||
return !this.get('topic_visible');
|
return this.topicLookup[this.topic_id];
|
||||||
}.property('topic_hidden'),
|
}.property(),
|
||||||
|
|
||||||
flaggedForSpam: function() {
|
flaggedForSpam: function() {
|
||||||
return !_.every(this.get('post_actions'), function(action) { return action.name_key !== 'spam'; });
|
return !_.every(this.get('post_actions'), function(action) { return action.name_key !== 'spam'; });
|
||||||
@ -80,7 +85,7 @@ Discourse.FlaggedPost = Discourse.Post.extend({
|
|||||||
}.property('post_actions.@each.targets_topic'),
|
}.property('post_actions.@each.targets_topic'),
|
||||||
|
|
||||||
canDeleteAsSpammer: function() {
|
canDeleteAsSpammer: function() {
|
||||||
return (Discourse.User.currentProp('staff') && this.get('flaggedForSpam') && this.get('user.can_delete_all_posts') && this.get('user.can_be_deleted'));
|
return Discourse.User.currentProp('staff') && this.get('flaggedForSpam') && this.get('user.can_delete_all_posts') && this.get('user.can_be_deleted');
|
||||||
}.property('flaggedForSpam'),
|
}.property('flaggedForSpam'),
|
||||||
|
|
||||||
deletePost: function() {
|
deletePost: function() {
|
||||||
@ -91,28 +96,24 @@ Discourse.FlaggedPost = Discourse.Post.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
disagreeFlags: function() {
|
disagreeFlags: function () {
|
||||||
return Discourse.ajax('/admin/flags/disagree/' + this.id, { type: 'POST', cache: false });
|
return Discourse.ajax('/admin/flags/disagree/' + this.id, { type: 'POST', cache: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
deferFlags: function() {
|
deferFlags: function (deletePost) {
|
||||||
return Discourse.ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false });
|
return Discourse.ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } });
|
||||||
},
|
},
|
||||||
|
|
||||||
agreeFlags: function() {
|
agreeFlags: function (deletePost) {
|
||||||
return Discourse.ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false });
|
return Discourse.ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } });
|
||||||
},
|
},
|
||||||
|
|
||||||
postHidden: Em.computed.alias('hidden'),
|
postHidden: Em.computed.alias('hidden'),
|
||||||
|
|
||||||
extraClasses: function() {
|
extraClasses: function() {
|
||||||
var classes = [];
|
var classes = [];
|
||||||
if (this.get('hidden')) {
|
if (this.get('hidden')) { classes.push('hidden-post'); }
|
||||||
classes.push('hidden-post');
|
if (this.get('deleted')) { classes.push('deleted'); }
|
||||||
}
|
|
||||||
if (this.get('deleted')){
|
|
||||||
classes.push('deleted');
|
|
||||||
}
|
|
||||||
return classes.join(' ');
|
return classes.join(' ');
|
||||||
}.property(),
|
}.property(),
|
||||||
|
|
||||||
@ -121,26 +122,36 @@ Discourse.FlaggedPost = Discourse.Post.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
Discourse.FlaggedPost.reopenClass({
|
Discourse.FlaggedPost.reopenClass({
|
||||||
findAll: function(filter, offset) {
|
findAll: function (filter, offset) {
|
||||||
|
|
||||||
offset = offset || 0;
|
offset = offset || 0;
|
||||||
|
|
||||||
var result = Em.A();
|
var result = Em.A();
|
||||||
result.set('loading', true);
|
result.set('loading', true);
|
||||||
return Discourse.ajax('/admin/flags/' + filter + '.json?offset=' + offset).then(function(data) {
|
|
||||||
|
return Discourse.ajax('/admin/flags/' + filter + '.json?offset=' + offset).then(function (data) {
|
||||||
|
// users
|
||||||
var userLookup = {};
|
var userLookup = {};
|
||||||
_.each(data.users,function(user) {
|
_.each(data.users,function (user) {
|
||||||
userLookup[user.id] = Discourse.AdminUser.create(user);
|
userLookup[user.id] = Discourse.AdminUser.create(user);
|
||||||
});
|
});
|
||||||
_.each(data.posts,function(post) {
|
|
||||||
|
// topics
|
||||||
|
var topicLookup = {};
|
||||||
|
_.each(data.topics, function (topic) {
|
||||||
|
topicLookup[topic.id] = Discourse.Topic.create(topic);
|
||||||
|
});
|
||||||
|
|
||||||
|
// posts
|
||||||
|
_.each(data.posts,function (post) {
|
||||||
var f = Discourse.FlaggedPost.create(post);
|
var f = Discourse.FlaggedPost.create(post);
|
||||||
f.userLookup = userLookup;
|
f.userLookup = userLookup;
|
||||||
|
f.topicLookup = topicLookup;
|
||||||
result.pushObject(f);
|
result.pushObject(f);
|
||||||
});
|
});
|
||||||
|
|
||||||
result.set('loading', false);
|
result.set('loading', false);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,7 +18,16 @@ Discourse.AdminFlagsRouteType = Discourse.Route.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
Discourse.AdminFlagsActiveRoute = Discourse.AdminFlagsRouteType.extend({
|
Discourse.AdminFlagsActiveRoute = Discourse.AdminFlagsRouteType.extend({
|
||||||
filter: 'active'
|
filter: 'active',
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
|
||||||
|
showDeleteFlagModal: function(flaggedPost) {
|
||||||
|
Discourse.Route.showModal(this, 'admin_delete_flag', flaggedPost);
|
||||||
|
this.controllerFor('modal').set('modalClass', 'delete-flag-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin-container">
|
<div class="admin-container">
|
||||||
{{#if model.loading}}
|
{{#if loading}}
|
||||||
<div class='admin-loading'>{{i18n loading}}</div>
|
<div class='admin-loading'>{{i18n loading}}</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if model.length}}
|
{{#if length}}
|
||||||
<table class='admin-flags'>
|
<table class='admin-flags'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -19,8 +19,6 @@
|
|||||||
<th class='excerpt'></th>
|
<th class='excerpt'></th>
|
||||||
<th class='flaggers'>{{i18n admin.flags.flagged_by}}</th>
|
<th class='flaggers'>{{i18n admin.flags.flagged_by}}</th>
|
||||||
<th class='flaggers'>{{#if adminOldFlagsView}}{{i18n admin.flags.resolved_by}}{{/if}}</th>
|
<th class='flaggers'>{{#if adminOldFlagsView}}{{i18n admin.flags.resolved_by}}{{/if}}</th>
|
||||||
<th class='last-flagged'></th>
|
|
||||||
<th class='action'></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -36,8 +34,13 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class='excerpt'>
|
<td class='excerpt'>
|
||||||
{{#if flaggedPost.topicHidden}}<i title='{{i18n topic_statuses.invisible.help}}' class='fa fa-eye-slash'></i> {{/if}}<h3><a href='{{unbound flaggedPost.url}}'>{{flaggedPost.title}}</a></h3>
|
<h3>
|
||||||
<br>
|
{{#if flaggedPost.topic.isPrivateMessage}}
|
||||||
|
<span class="private-message-glyph">{{icon envelope}}</span>
|
||||||
|
{{/if}}
|
||||||
|
{{topic-status topic=flaggedPost.topic}}
|
||||||
|
<a href='{{unbound flaggedPost.topic.url}}'>{{flaggedPost.topic.title}}</a>
|
||||||
|
</h3>
|
||||||
{{#if flaggedPost.postAuthorFlagged}}
|
{{#if flaggedPost.postAuthorFlagged}}
|
||||||
{{{flaggedPost.excerpt}}}
|
{{{flaggedPost.excerpt}}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@ -48,14 +51,16 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{{#each flaggedPost.flaggers}}
|
{{#each flaggedPost.flaggers}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td width="20%">
|
||||||
{{#link-to 'adminUser' this.user}}{{avatar this.user imageSize="small"}} {{/link-to}}
|
{{#link-to 'adminUser' user}}
|
||||||
|
{{avatar user imageSize="small"}}
|
||||||
|
{{/link-to}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td width="30%">
|
||||||
{{date this.flaggedAt}}
|
{{date flaggedAt}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td width="50%">
|
||||||
{{this.flagType}}
|
{{flagType}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@ -68,52 +73,62 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{{#each flaggedPost.flaggers}}
|
{{#each flaggedPost.flaggers}}
|
||||||
<tr>
|
<tr>
|
||||||
{{#if deletedBy}}
|
<td width="20%">
|
||||||
<td>
|
{{#link-to 'adminUser' disposedBy}}
|
||||||
{{#link-to 'adminUser' this.deletedBy}}{{avatar this.deletedBy imageSize="small"}} {{/link-to}}
|
{{avatar disposedBy imageSize="small"}}
|
||||||
|
{{/link-to}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td width="30%">
|
||||||
{{#if this.tookAction}}
|
{{date disposedAt}}
|
||||||
|
</td>
|
||||||
|
<td width="50%">
|
||||||
|
{{disposition}}
|
||||||
|
{{#if tookAction}}
|
||||||
<i class='fa fa-gavel'></i>
|
<i class='fa fa-gavel'></i>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
{{date this.deletedAt}}
|
|
||||||
</td>
|
|
||||||
{{/if}}
|
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{{#if flaggedPost.topicFlagged}}
|
{{#if flaggedPost.topicFlagged}}
|
||||||
<tr>
|
<tr class='message'>
|
||||||
<td></td>
|
|
||||||
<td class='message'><div>{{{i18n admin.flags.topic_flagged}}}</div></td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
<td></td>
|
||||||
|
<td colspan="3">
|
||||||
|
<div>
|
||||||
|
{{{i18n admin.flags.topic_flagged}}}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#each flaggedPost.messages}}
|
{{#each flaggedPost.conversations}}
|
||||||
<tr>
|
<tr class='message'>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td class='message'>
|
<td colspan="3">
|
||||||
<div>
|
<div>
|
||||||
{{#unless bySystemUser}}
|
{{#if response}}
|
||||||
{{#link-to 'adminUser' user}}{{avatar user imageSize="small"}}{{/link-to}}
|
<p>
|
||||||
{{message}}
|
{{#link-to 'adminUser' response.user}}{{avatar response.user imageSize="small"}}{{/link-to}} {{{response.excerpt}}}
|
||||||
<a href="{{unbound permalink}}"><button class='btn'><i class="fa fa-reply"></i> {{i18n admin.flags.view_message}}</button></a>
|
</p>
|
||||||
{{else}}
|
{{#if reply}}
|
||||||
<b>{{i18n admin.flags.system}}</b>:
|
<p>
|
||||||
{{message}}
|
{{#link-to 'adminUser' reply.user}}{{avatar reply.user imageSize="small"}}{{/link-to}} {{{reply.excerpt}}}
|
||||||
{{/unless}}
|
{{#if hasMore}}
|
||||||
|
<a href="{{unbound permalink}}">{{i18n admin.flags.more}}</a>
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
<a href="{{unbound permalink}}">
|
||||||
|
<button class='btn btn-reply'><i class="fa fa-reply"></i> {{i18n admin.flags.reply_message}}</button>
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
@ -126,18 +141,13 @@
|
|||||||
|
|
||||||
{{#if flaggedPost.postAuthorFlagged}}
|
{{#if flaggedPost.postAuthorFlagged}}
|
||||||
{{#if flaggedPost.postHidden}}
|
{{#if flaggedPost.postHidden}}
|
||||||
<button title='{{i18n admin.flags.disagree_unhide_title}}' class='btn' {{action disagreeFlags flaggedPost}}><i class="fa fa-thumbs-o-down"></i> {{i18n admin.flags.disagree_unhide}}</button>
|
<button title='{{i18n admin.flags.disagree_flag_unhide_post_title}}' class='btn' {{action disagreeFlags flaggedPost}}><i class="fa fa-thumbs-o-down"></i> {{i18n admin.flags.disagree_flag_unhide_post}}</button>
|
||||||
<button title='{{i18n admin.flags.defer_title}}' class='btn' {{action deferFlags flaggedPost}}><i class="fa fa-external-link"></i> {{i18n admin.flags.defer}}</button>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<button title='{{i18n admin.flags.agree_hide_title}}' class='btn' {{action agreeFlags flaggedPost}}><i class="fa fa-thumbs-o-up"></i> {{i18n admin.flags.agree_hide}}</button>
|
<button title='{{i18n admin.flags.agree_flag_hide_post_title}}' class='btn btn-primary' {{action agreeFlags flaggedPost}}><i class="fa fa-thumbs-o-up"></i> {{i18n admin.flags.agree_flag_hide_post}}</button>
|
||||||
<button title='{{i18n admin.flags.disagree_title}}' class='btn' {{action disagreeFlags flaggedPost}}><i class="fa fa-thumbs-o-down"></i> {{i18n admin.flags.disagree}}</button>
|
<button title='{{i18n admin.flags.disagree_flag_title}}' class='btn' {{action disagreeFlags flaggedPost}}><i class="fa fa-thumbs-o-down"></i> {{i18n admin.flags.disagree_flag}}</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<button title='{{i18n admin.flags.defer_flag_title}}' class='btn' {{action deferFlags flaggedPost}}><i class="fa fa-external-link"></i> {{i18n admin.flags.defer_flag}}</button>
|
||||||
{{#if flaggedPost.canDeleteAsSpammer}}
|
<button title='{{i18n admin.flags.delete_title}}' class='btn btn-danger' {{action showDeleteFlagModal flaggedPost}}><i class="fa fa-trash-o"></i> {{i18n admin.flags.delete}}</button>
|
||||||
<button title='{{i18n admin.flags.delete_spammer_title}}' class="btn" {{action deleteSpammer flaggedPost}}><i class="fa fa-exclamation-triangle"></i> {{i18n flagging.delete_spammer}}</button>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<button title='{{i18n admin.flags.delete_post_title}}' class='btn' {{action deletePost flaggedPost}}><i class="fa fa-trash-o"></i> {{i18n admin.flags.delete_post}}</button>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<button title='{{i18n admin.flags.clear_topic_flags_title}}' class='btn' {{action doneTopicFlags flaggedPost}}>{{i18n admin.flags.clear_topic_flags}}</button>
|
<button title='{{i18n admin.flags.clear_topic_flags_title}}' class='btn' {{action doneTopicFlags flaggedPost}}>{{i18n admin.flags.clear_topic_flags}}</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
<button title="{{i18n admin.flags.delete_post_defer_flag_title}}" {{action deletePostDeferFlag}} class="btn btn-primary"><i class="fa fa-trash-o"></i> {{i18n admin.flags.delete_post_defer_flag}}</button>
|
||||||
|
<button title="{{i18n admin.flags.delete_post_agree_flag_title}}" {{action deletePostAgreeFlag}} class="btn btn-primary"><i class="fa fa-trash-o"></i> {{i18n admin.flags.delete_post_agree_flag}}</button>
|
||||||
|
{{#if canDeleteAsSpammer}}
|
||||||
|
<button title="{{i18n admin.flags.delete_spammer_title}}" {{action deleteSpammer}} class="btn btn-danger"><i class="fa fa-exclamation-triangle"></i> {{i18n admin.flags.delete_spammer}}</button>
|
||||||
|
{{/if}}
|
@ -1,13 +1,21 @@
|
|||||||
Discourse.AdminFlagsView = Discourse.View.extend(Discourse.LoadMore, {
|
Discourse.AdminFlagsView = Discourse.View.extend(Discourse.LoadMore, {
|
||||||
loading: false,
|
loading: false,
|
||||||
eyelineSelector: '.admin-flags tbody tr',
|
eyelineSelector: '.admin-flags tbody tr',
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
|
||||||
loadMore: function() {
|
loadMore: function() {
|
||||||
var view = this;
|
var self = this;
|
||||||
if(this.get("loading") || this.get("model.allLoaded")) { return; }
|
|
||||||
|
if (this.get("loading") || this.get("model.allLoaded")) { return; }
|
||||||
|
|
||||||
this.set("loading", true);
|
this.set("loading", true);
|
||||||
this.get("controller").loadMore().then(function(){
|
|
||||||
view.set("loading", false);
|
this.get("controller").loadMore().then(function () {
|
||||||
|
self.set("loading", false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
A modal view for deleting a flag.
|
||||||
|
|
||||||
|
@class AdminDeleteFlagView
|
||||||
|
@extends Discourse.ModalBodyView
|
||||||
|
@namespace Discourse
|
||||||
|
@module Discourse
|
||||||
|
**/
|
||||||
|
Discourse.AdminDeleteFlagView = Discourse.ModalBodyView.extend({
|
||||||
|
templateName: 'admin/templates/modal/admin_delete_flag',
|
||||||
|
title: I18n.t('admin.flags.delete_flag_modal_title')
|
||||||
|
});
|
@ -53,7 +53,7 @@ export default Em.Component.extend({
|
|||||||
renderActionIf('usersCollapsed', 'who-acted', c.get('description'));
|
renderActionIf('usersCollapsed', 'who-acted', c.get('description'));
|
||||||
renderActionIf('canAlsoAction', 'act', I18n.t("post.actions.it_too." + c.get('actionType.name_key')));
|
renderActionIf('canAlsoAction', 'act', I18n.t("post.actions.it_too." + c.get('actionType.name_key')));
|
||||||
renderActionIf('can_undo', 'undo', I18n.t("post.actions.undo." + c.get('actionType.name_key')));
|
renderActionIf('can_undo', 'undo', I18n.t("post.actions.undo." + c.get('actionType.name_key')));
|
||||||
renderActionIf('can_clear_flags', 'clear-flags', I18n.t("post.actions.clear_flags", { count: c.count }));
|
renderActionIf('can_defer_flags', 'defer-flags', I18n.t("post.actions.defer_flags", { count: c.count }));
|
||||||
|
|
||||||
buffer.push("</div>");
|
buffer.push("</div>");
|
||||||
});
|
});
|
||||||
@ -77,8 +77,8 @@ export default Em.Component.extend({
|
|||||||
var $target = $(e.target),
|
var $target = $(e.target),
|
||||||
actionTypeId;
|
actionTypeId;
|
||||||
|
|
||||||
if (actionTypeId = $target.data('clear-flags')) {
|
if (actionTypeId = $target.data('defer-flags')) {
|
||||||
this.actionTypeById(actionTypeId).clearFlags();
|
this.actionTypeById(actionTypeId).deferFlags();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +47,7 @@ export default Ember.Component.extend({
|
|||||||
// Allow a plugin to add a custom icon to a topic
|
// Allow a plugin to add a custom icon to a topic
|
||||||
this.trigger('addCustomIcon', buffer);
|
this.trigger('addCustomIcon', buffer);
|
||||||
|
|
||||||
var togglePin = function(){
|
var togglePin = function () {};
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
renderIconIf('topic.closed', 'lock', 'locked');
|
renderIconIf('topic.closed', 'lock', 'locked');
|
||||||
renderIconIf('topic.archived', 'lock', 'archived');
|
renderIconIf('topic.archived', 'lock', 'archived');
|
||||||
|
@ -68,7 +68,7 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
|||||||
|
|
||||||
if(action === 'notify_moderators' || action === 'notify_user') {
|
if(action === 'notify_moderators' || action === 'notify_user') {
|
||||||
this.set('can_undo',false);
|
this.set('can_undo',false);
|
||||||
this.set('can_clear_flags',false);
|
this.set('can_defer_flags',false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add ourselves to the users who liked it if present
|
// Add ourselves to the users who liked it if present
|
||||||
@ -108,9 +108,9 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
clearFlags: function() {
|
deferFlags: function() {
|
||||||
var actionSummary = this;
|
var actionSummary = this;
|
||||||
return Discourse.ajax("/post_actions/clear_flags", {
|
return Discourse.ajax("/post_actions/defer_flags", {
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data: {
|
data: {
|
||||||
post_action_type_id: this.get('id'),
|
post_action_type_id: this.get('id'),
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
{{#if editingTopic}}
|
{{#if editingTopic}}
|
||||||
{{#if isPrivateMessage}}
|
{{#if isPrivateMessage}}
|
||||||
<span class="private-message-glyph"><i class='fa fa-envelope'></i></span>
|
<span class="private-message-glyph">{{icon envelope}}</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{category-chooser valueAttribute="id" value=newCategoryId source=category_id}}
|
{{category-chooser valueAttribute="id" value=newCategoryId source=category_id}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -494,25 +494,30 @@ section.details {
|
|||||||
|
|
||||||
.admin-flags {
|
.admin-flags {
|
||||||
|
|
||||||
tr.hidden-post td.excerpt { opacity: 0.4; }
|
.hidden-post td.excerpt { opacity: 0.5; }
|
||||||
tr.deleted td.excerpt { opacity: 0.8; background-color: scale-color($danger, $lightness: 30%); }
|
.deleted td.excerpt { background-color: scale-color($danger, $lightness: 70%); }
|
||||||
td.message {
|
.message { background-color: scale-color($highlight, $lightness: 70%); }
|
||||||
padding: 4px 8px;
|
.message:hover { background-color: scale-color($highlight, $lightness: 30%); }
|
||||||
background-color: scale-color($highlight, $lightness: 30%);
|
|
||||||
}
|
|
||||||
td { vertical-align: top; }
|
td { vertical-align: top; }
|
||||||
th { text-align: left; }
|
th { text-align: left; }
|
||||||
.user { width: 40px; padding-top: 12px; }
|
.user {
|
||||||
|
width: 20px;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
.excerpt {
|
.excerpt {
|
||||||
max-width: 740px;
|
max-width: 720px;
|
||||||
width: 740px;
|
width: 720px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
.fa,h3 { display: inline-block; }
|
.fa { display: inline-block; }
|
||||||
|
h3 {
|
||||||
|
max-height: 1.2em;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.flaggers {
|
.flaggers {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
padding: 8px 0 0 5px;
|
||||||
td {
|
td {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
@ -523,6 +528,10 @@ section.details {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
td p {
|
||||||
|
font-size: 13px;
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dashboard */
|
/* Dashboard */
|
||||||
@ -1135,6 +1144,17 @@ button.ru {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete-flag-modal {
|
||||||
|
.modal-inner-container {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
margin: 10px 0 10px 10px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media all
|
@media all
|
||||||
and (max-width : 850px) {
|
and (max-width : 850px) {
|
||||||
.nav-stacked {
|
.nav-stacked {
|
||||||
@ -1202,7 +1222,6 @@ and (max-width : 500px) {
|
|||||||
|
|
||||||
.customize .content-list, .customize .current-style {
|
.customize .content-list, .customize .current-style {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +1,50 @@
|
|||||||
require 'flag_query'
|
require 'flag_query'
|
||||||
|
|
||||||
class Admin::FlagsController < Admin::AdminController
|
class Admin::FlagsController < Admin::AdminController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
# we may get out of sync, fix it here
|
# we may get out of sync, fix it here
|
||||||
PostAction.update_flagged_posts_count
|
PostAction.update_flagged_posts_count
|
||||||
posts, users = FlagQuery.flagged_posts_report(current_user, params[:filter], params[:offset].to_i, 10)
|
posts, topics, users = FlagQuery.flagged_posts_report(current_user, params[:filter], params[:offset].to_i, 10)
|
||||||
|
|
||||||
if posts.blank?
|
if posts.blank?
|
||||||
render json: {users: [], posts: []}
|
render json: { posts: [], topics: [], users: [] }
|
||||||
else
|
else
|
||||||
render json: MultiJson.dump({users: serialize_data(users, AdminDetailedUserSerializer), posts: posts})
|
render json: MultiJson.dump({
|
||||||
|
posts: posts,
|
||||||
|
topics: serialize_data(topics, FlaggedTopicSerializer),
|
||||||
|
users: serialize_data(users, FlaggedUserSerializer)
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def disagree
|
|
||||||
p = Post.find(params[:id])
|
|
||||||
PostAction.clear_flags!(p, current_user.id)
|
|
||||||
p.reload
|
|
||||||
p.unhide!
|
|
||||||
render nothing: true
|
|
||||||
end
|
|
||||||
|
|
||||||
def agree
|
def agree
|
||||||
p = Post.find(params[:id])
|
params.permit(:id, :delete_post)
|
||||||
post_action_type = PostAction.post_action_type_for_post(p.id)
|
post = Post.find(params[:id])
|
||||||
PostAction.defer_flags!(p, current_user.id)
|
post_action_type = PostAction.post_action_type_for_post(post.id)
|
||||||
PostAction.hide_post!(p, post_action_type)
|
PostAction.agree_flags!(post, current_user, params[:delete_post])
|
||||||
|
if params[:delete_post]
|
||||||
|
PostDestroyer.new(current_user, post).destroy
|
||||||
|
else
|
||||||
|
PostAction.hide_post!(post, post_action_type)
|
||||||
|
end
|
||||||
|
render nothing: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def disagree
|
||||||
|
params.permit(:id)
|
||||||
|
post = Post.find(params[:id])
|
||||||
|
PostAction.clear_flags!(post, current_user)
|
||||||
|
post.reload
|
||||||
|
post.unhide!
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
||||||
def defer
|
def defer
|
||||||
p = Post.find(params[:id])
|
params.permit(:id, :delete_post)
|
||||||
PostAction.defer_flags!(p, current_user.id)
|
post = Post.find(params[:id])
|
||||||
|
PostAction.defer_flags!(post, current_user, params[:delete_post])
|
||||||
|
PostDestroyer.new(current_user, post).destroy if params[:delete_post]
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ class Admin::UsersController < Admin::AdminController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
user = User.find_by(id: params[:id])
|
user = User.find_by(id: params[:id].to_i)
|
||||||
guardian.ensure_can_delete_user!(user)
|
guardian.ensure_can_delete_user!(user)
|
||||||
begin
|
begin
|
||||||
if UserDestroyer.new(current_user).destroy(user, params.slice(:delete_posts, :block_email, :block_urls, :block_ip, :context))
|
if UserDestroyer.new(current_user).destroy(user, params.slice(:delete_posts, :block_email, :block_urls, :block_ip, :context))
|
||||||
|
@ -11,7 +11,7 @@ class PostActionsController < ApplicationController
|
|||||||
|
|
||||||
args = {}
|
args = {}
|
||||||
args[:message] = params[:message] if params[:message].present?
|
args[:message] = params[:message] if params[:message].present?
|
||||||
args[:take_action] = true if guardian.is_staff? and params[:take_action] == 'true'
|
args[:take_action] = true if guardian.is_staff? && params[:take_action] == 'true'
|
||||||
args[:flag_topic] = true if params[:flag_topic] == 'true'
|
args[:flag_topic] = true if params[:flag_topic] == 'true'
|
||||||
|
|
||||||
post_action = PostAction.act(current_user, @post, @post_action_type_id, args)
|
post_action = PostAction.act(current_user, @post, @post_action_type_id, args)
|
||||||
@ -46,17 +46,17 @@ class PostActionsController < ApplicationController
|
|||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_flags
|
def defer_flags
|
||||||
guardian.ensure_can_clear_flags!(@post)
|
guardian.ensure_can_defer_flags!(@post)
|
||||||
|
|
||||||
PostAction.clear_flags!(@post, current_user.id, @post_action_type_id)
|
PostAction.defer_flags!(@post, current_user)
|
||||||
@post.reload
|
@post.reload
|
||||||
|
|
||||||
if @post.is_flagged?
|
if @post.is_flagged?
|
||||||
render json: {success: true, hidden: true}
|
render json: { success: true, hidden: true }
|
||||||
else
|
else
|
||||||
@post.unhide!
|
@post.unhide!
|
||||||
render json: {success: true, hidden: false}
|
render json: { success: true, hidden: false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -16,17 +16,35 @@ class PostAction < ActiveRecord::Base
|
|||||||
rate_limit :post_action_rate_limiter
|
rate_limit :post_action_rate_limiter
|
||||||
|
|
||||||
scope :spam_flags, -> { where(post_action_type_id: PostActionType.types[:spam]) }
|
scope :spam_flags, -> { where(post_action_type_id: PostActionType.types[:spam]) }
|
||||||
|
scope :flags, -> { where(post_action_type_id: PostActionType.notify_flag_type_ids) }
|
||||||
|
scope :publics, -> { where(post_action_type_id: PostActionType.public_type_ids) }
|
||||||
|
scope :active, -> { where(defered_at: nil, agreed_at: nil, deleted_at: nil) }
|
||||||
|
|
||||||
after_save :update_counters
|
after_save :update_counters
|
||||||
after_save :enforce_rules
|
after_save :enforce_rules
|
||||||
after_commit :notify_subscribers
|
after_commit :notify_subscribers
|
||||||
|
|
||||||
|
def disposed_by_id
|
||||||
|
deleted_by_id || agreed_by_id || defered_by_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def disposed_at
|
||||||
|
deleted_at || agreed_at || defered_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def disposition
|
||||||
|
return :disagreed if deleted_at
|
||||||
|
return :agreed if agreed_at
|
||||||
|
return :defered if defered_at
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def self.update_flagged_posts_count
|
def self.update_flagged_posts_count
|
||||||
posts_flagged_count = PostAction.joins(post: :topic)
|
posts_flagged_count = PostAction.active
|
||||||
.where('defer = false or defer IS NULL')
|
.flags
|
||||||
.where('post_actions.post_action_type_id' => PostActionType.notify_flag_type_ids,
|
.joins(post: :topic)
|
||||||
'posts.deleted_at' => nil,
|
.where('posts.deleted_at' => nil)
|
||||||
'topics.deleted_at' => nil)
|
.where('topics.deleted_at' => nil)
|
||||||
.count('DISTINCT posts.id')
|
.count('DISTINCT posts.id')
|
||||||
|
|
||||||
$redis.set('posts_flagged_count', posts_flagged_count)
|
$redis.set('posts_flagged_count', posts_flagged_count)
|
||||||
@ -41,56 +59,91 @@ class PostAction < ActiveRecord::Base
|
|||||||
def self.counts_for(collection, user)
|
def self.counts_for(collection, user)
|
||||||
return {} if collection.blank?
|
return {} if collection.blank?
|
||||||
|
|
||||||
collection_ids = collection.map {|p| p.id}
|
collection_ids = collection.map(&:id)
|
||||||
user_id = user.present? ? user.id : 0
|
user_id = user.present? ? user.id : 0
|
||||||
|
|
||||||
result = PostAction.where(post_id: collection_ids, user_id: user_id)
|
post_actions = PostAction.where(post_id: collection_ids, user_id: user_id)
|
||||||
|
|
||||||
user_actions = {}
|
user_actions = {}
|
||||||
result.each do |r|
|
post_actions.each do |post_action|
|
||||||
user_actions[r.post_id] ||= {}
|
user_actions[post_action.post_id] ||= {}
|
||||||
user_actions[r.post_id][r.post_action_type_id] = r
|
user_actions[post_action.post_id][post_action.post_action_type_id] = post_action
|
||||||
end
|
end
|
||||||
|
|
||||||
user_actions
|
user_actions
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.count_per_day_for_type(sinceDaysAgo = 30, post_action_type)
|
def self.count_per_day_for_type(post_action_type, since_days_ago=30)
|
||||||
unscoped.where(post_action_type_id: post_action_type).where('created_at > ?', sinceDaysAgo.days.ago).group('date(created_at)').order('date(created_at)').count
|
unscoped.where(post_action_type_id: post_action_type)
|
||||||
|
.where('created_at > ?', since_days_ago.days.ago)
|
||||||
|
.group('date(created_at)')
|
||||||
|
.order('date(created_at)')
|
||||||
|
.count
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.clear_flags!(post, moderator_id, action_type_id = nil)
|
def self.agree_flags!(post, moderator, delete_post=false)
|
||||||
# -1 is the automatic system cleary
|
actions = PostAction.active
|
||||||
actions = if action_type_id
|
.where(post_id: post.id)
|
||||||
[action_type_id]
|
.where(post_action_type_id: PostActionType.flag_types.values)
|
||||||
else
|
|
||||||
moderator_id == -1 ? PostActionType.auto_action_flag_types.values : PostActionType.flag_types.values
|
|
||||||
end
|
|
||||||
|
|
||||||
PostAction.where({ post_id: post.id, post_action_type_id: actions }).update_all({ deleted_at: Time.zone.now, deleted_by_id: moderator_id })
|
actions.each do |action|
|
||||||
f = actions.map{|t| ["#{PostActionType.types[t]}_count", 0]}
|
action.agreed_at = Time.zone.now
|
||||||
Post.where(id: post.id).with_deleted.update_all(Hash[*f.flatten])
|
action.agreed_by_id = moderator.id
|
||||||
update_flagged_posts_count
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.defer_flags!(post, moderator_id)
|
|
||||||
actions = PostAction.where(
|
|
||||||
defer: nil,
|
|
||||||
post_id: post.id,
|
|
||||||
post_action_type_id: PostActionType.flag_types.values,
|
|
||||||
deleted_at: nil
|
|
||||||
)
|
|
||||||
|
|
||||||
actions.each do |a|
|
|
||||||
a.defer = true
|
|
||||||
a.defer_by = moderator_id
|
|
||||||
# so callback is called
|
# so callback is called
|
||||||
a.save
|
action.save
|
||||||
|
action.add_moderator_post_if_needed(moderator, :agreed, delete_post)
|
||||||
end
|
end
|
||||||
|
|
||||||
update_flagged_posts_count
|
update_flagged_posts_count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.clear_flags!(post, moderator)
|
||||||
|
# -1 is the automatic system cleary
|
||||||
|
action_type_ids = moderator.id == -1 ?
|
||||||
|
PostActionType.auto_action_flag_types.values :
|
||||||
|
PostActionType.flag_types.values
|
||||||
|
|
||||||
|
actions = PostAction.where(post_id: post.id)
|
||||||
|
.where(post_action_type_id: action_type_ids)
|
||||||
|
|
||||||
|
actions.each do |action|
|
||||||
|
action.deleted_at = Time.zone.now
|
||||||
|
action.deleted_by_id = moderator.id
|
||||||
|
# so callback is called
|
||||||
|
action.save
|
||||||
|
action.add_moderator_post_if_needed(moderator, :disagreed)
|
||||||
|
end
|
||||||
|
|
||||||
|
# reset all cached counters
|
||||||
|
f = action_type_ids.map { |t| ["#{PostActionType.types[t]}_count", 0] }
|
||||||
|
Post.with_deleted.where(id: post.id).update_all(Hash[*f.flatten])
|
||||||
|
|
||||||
|
update_flagged_posts_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.defer_flags!(post, moderator, delete_post=false)
|
||||||
|
actions = PostAction.active
|
||||||
|
.where(post_id: post.id)
|
||||||
|
.where(post_action_type_id: PostActionType.flag_types.values)
|
||||||
|
|
||||||
|
actions.each do |action|
|
||||||
|
action.defered_at = Time.zone.now
|
||||||
|
action.defered_by_id = moderator.id
|
||||||
|
# so callback is called
|
||||||
|
action.save
|
||||||
|
action.add_moderator_post_if_needed(moderator, :defered, delete_post)
|
||||||
|
end
|
||||||
|
|
||||||
|
update_flagged_posts_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_moderator_post_if_needed(moderator, disposition, delete_post=false)
|
||||||
|
return unless related_post
|
||||||
|
message_key = "flags_dispositions.#{disposition}"
|
||||||
|
message_key << "_and_deleted" if delete_post
|
||||||
|
related_post.topic.add_moderator_post(moderator, I18n.t(message_key))
|
||||||
|
end
|
||||||
|
|
||||||
def self.create_message_for_post_action(user, post, post_action_type_id, opts)
|
def self.create_message_for_post_action(user, post, post_action_type_id, opts)
|
||||||
post_action_type = PostActionType.types[post_action_type_id]
|
post_action_type = PostActionType.types[post_action_type_id]
|
||||||
|
|
||||||
@ -123,10 +176,10 @@ class PostAction < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.act(user, post, post_action_type_id, opts={})
|
def self.act(user, post, post_action_type_id, opts={})
|
||||||
|
related_post_id = create_message_for_post_action(user, post, post_action_type_id, opts)
|
||||||
|
staff_took_action = opts[:take_action] || false
|
||||||
|
|
||||||
related_post_id = create_message_for_post_action(user,post,post_action_type_id,opts)
|
targets_topic = if opts[:flag_topic] && post.topic
|
||||||
|
|
||||||
targets_topic = if opts[:flag_topic] and post.topic
|
|
||||||
post.topic.reload
|
post.topic.reload
|
||||||
post.topic.posts_count != 1
|
post.topic.posts_count != 1
|
||||||
end
|
end
|
||||||
@ -138,8 +191,7 @@ class PostAction < ActiveRecord::Base
|
|||||||
}
|
}
|
||||||
|
|
||||||
action_attributes = {
|
action_attributes = {
|
||||||
message: opts[:message],
|
staff_took_action: staff_took_action,
|
||||||
staff_took_action: opts[:take_action] || false,
|
|
||||||
related_post_id: related_post_id,
|
related_post_id: related_post_id,
|
||||||
targets_topic: !!targets_topic
|
targets_topic: !!targets_topic
|
||||||
}
|
}
|
||||||
@ -157,9 +209,13 @@ class PostAction < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
post_action = PostAction.where(where_attrs).first
|
post_action = PostAction.where(where_attrs).first
|
||||||
post_action.update_counters
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# agree with other flags
|
||||||
|
PostAction.agree_flags!(post, user) if staff_took_action
|
||||||
|
# update counters
|
||||||
|
post_action.try(:update_counters)
|
||||||
|
|
||||||
post_action
|
post_action
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
# can happen despite being .create
|
# can happen despite being .create
|
||||||
@ -216,10 +272,11 @@ class PostAction < ActiveRecord::Base
|
|||||||
|
|
||||||
before_create do
|
before_create do
|
||||||
post_action_type_ids = is_flag? ? PostActionType.flag_types.values : post_action_type_id
|
post_action_type_ids = is_flag? ? PostActionType.flag_types.values : post_action_type_id
|
||||||
raise AlreadyActed if PostAction.where(user_id: user_id,
|
raise AlreadyActed if PostAction.where(user_id: user_id)
|
||||||
post_id: post_id,
|
.where(post_id: post_id)
|
||||||
post_action_type_id: post_action_type_ids,
|
.where(post_action_type_id: post_action_type_ids)
|
||||||
deleted_at: nil)
|
.where(deleted_at: nil)
|
||||||
|
.where(targets_topic: targets_topic)
|
||||||
.exists?
|
.exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -251,30 +308,30 @@ class PostAction < ActiveRecord::Base
|
|||||||
PostActionType.types[post_action_type_id]
|
PostActionType.types[post_action_type_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def update_counters
|
def update_counters
|
||||||
# Update denormalized counts
|
# Update denormalized counts
|
||||||
column = "#{post_action_type_key.to_s}_count"
|
column = "#{post_action_type_key.to_s}_count"
|
||||||
delta = deleted_at.nil? ? 1 : -1
|
count = PostAction.where(post_id: post_id)
|
||||||
|
.where(post_action_type_id: post_action_type_id)
|
||||||
|
.count
|
||||||
|
|
||||||
# We probably want to refactor this method to something cleaner.
|
# We probably want to refactor this method to something cleaner.
|
||||||
case post_action_type_key
|
case post_action_type_key
|
||||||
when :vote
|
when :vote
|
||||||
# Voting also changes the sort_order
|
# Voting also changes the sort_order
|
||||||
Post.where(id: post_id).update_all ["vote_count = vote_count + :delta, sort_order = :max - (vote_count + :delta)",
|
Post.where(id: post_id).update_all ["vote_count = :count, sort_order = :max - :count", count: count, max: Topic.max_sort_order]
|
||||||
delta: delta,
|
|
||||||
max: Topic.max_sort_order]
|
|
||||||
when :like
|
when :like
|
||||||
# `like_score` is weighted higher for staff accounts
|
# `like_score` is weighted higher for staff accounts
|
||||||
Post.where(id: post_id).update_all ["like_count = like_count + :delta, like_score = like_score + :score_delta",
|
score = PostAction.joins(:user)
|
||||||
delta: delta,
|
.where(post_id: post_id)
|
||||||
score_delta: user.staff? ? delta * SiteSetting.staff_like_weight : delta]
|
.sum("CASE WHEN users.moderator OR users.admin THEN #{SiteSetting.staff_like_weight} ELSE 1 END")
|
||||||
|
Post.where(id: post_id).update_all ["like_count = :count, like_score = :score", count: count, score: score]
|
||||||
else
|
else
|
||||||
Post.where(id: post_id).update_all ["#{column} = #{column} + ?", delta]
|
Post.where(id: post_id).update_all ["#{column} = ?", count]
|
||||||
end
|
end
|
||||||
|
|
||||||
post = Post.with_deleted.where(id: post_id).first
|
topic_id = Post.with_deleted.where(id: post_id).pluck(:topic_id).first
|
||||||
Topic.where(id: post.topic_id).update_all ["#{column} = #{column} + ?", delta]
|
Topic.where(id: topic_id).update_all ["#{column} = ?", count]
|
||||||
|
|
||||||
if PostActionType.notify_flag_type_ids.include?(post_action_type_id)
|
if PostActionType.notify_flag_type_ids.include?(post_action_type_id)
|
||||||
PostAction.update_flagged_posts_count
|
PostAction.update_flagged_posts_count
|
||||||
@ -314,7 +371,6 @@ class PostAction < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def self.hide_post!(post, post_action_type, reason=nil)
|
def self.hide_post!(post, post_action_type, reason=nil)
|
||||||
return if post.hidden
|
return if post.hidden
|
||||||
|
|
||||||
@ -324,8 +380,7 @@ class PostAction < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
Post.where(id: post.id).update_all(["hidden = true, hidden_at = CURRENT_TIMESTAMP, hidden_reason_id = COALESCE(hidden_reason_id, ?)", reason])
|
Post.where(id: post.id).update_all(["hidden = true, hidden_at = CURRENT_TIMESTAMP, hidden_reason_id = COALESCE(hidden_reason_id, ?)", reason])
|
||||||
Topic.where(["id = :topic_id AND NOT EXISTS(SELECT 1 FROM POSTS WHERE topic_id = :topic_id AND NOT hidden)",
|
Topic.where(["id = :topic_id AND NOT EXISTS(SELECT 1 FROM POSTS WHERE topic_id = :topic_id AND NOT hidden)", topic_id: post.topic_id]).update_all(visible: false)
|
||||||
topic_id: post.topic_id]).update_all(visible: false)
|
|
||||||
|
|
||||||
# inform user
|
# inform user
|
||||||
if post.user
|
if post.user
|
||||||
@ -345,7 +400,7 @@ class PostAction < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.post_action_type_for_post(post_id)
|
def self.post_action_type_for_post(post_id)
|
||||||
post_action = PostAction.find_by(defer: nil, post_id: post_id, post_action_type_id: PostActionType.flag_types.values, deleted_at: nil)
|
post_action = PostAction.find_by(defered_at: nil, post_id: post_id, post_action_type_id: PostActionType.flag_types.values, deleted_at: nil)
|
||||||
PostActionType.types[post_action.post_action_type_id]
|
PostActionType.types[post_action.post_action_type_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -366,15 +421,17 @@ end
|
|||||||
# user_id :integer not null
|
# user_id :integer not null
|
||||||
# post_action_type_id :integer not null
|
# post_action_type_id :integer not null
|
||||||
# deleted_at :datetime
|
# deleted_at :datetime
|
||||||
# created_at :datetime
|
# created_at :datetime not null
|
||||||
# updated_at :datetime
|
# updated_at :datetime not null
|
||||||
# deleted_by_id :integer
|
# deleted_by_id :integer
|
||||||
# message :text
|
# message :text
|
||||||
# related_post_id :integer
|
# related_post_id :integer
|
||||||
# staff_took_action :boolean default(FALSE), not null
|
# staff_took_action :boolean default(FALSE), not null
|
||||||
# defer :boolean
|
# defered_at :datetime
|
||||||
# defer_by :integer
|
# defer_by_id :integer
|
||||||
# targets_topic :boolean default(FALSE)
|
# targets_topic :boolean default(FALSE)
|
||||||
|
# agreed_at :datetime
|
||||||
|
# agreed_by_id :integer
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
@ -19,6 +19,10 @@ class PostActionType < ActiveRecord::Base
|
|||||||
@public_types ||= types.except(*flag_types.keys << :notify_user)
|
@public_types ||= types.except(*flag_types.keys << :notify_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def public_type_ids
|
||||||
|
@public_type_ids ||= public_types.values
|
||||||
|
end
|
||||||
|
|
||||||
def flag_types
|
def flag_types
|
||||||
@flag_types ||= types.only(:off_topic, :spam, :inappropriate, :notify_moderators)
|
@flag_types ||= types.only(:off_topic, :spam, :inappropriate, :notify_moderators)
|
||||||
end
|
end
|
||||||
|
@ -115,8 +115,8 @@ class Report
|
|||||||
|
|
||||||
def self.post_action_report(report, post_action_type)
|
def self.post_action_report(report, post_action_type)
|
||||||
report.data = []
|
report.data = []
|
||||||
PostAction.count_per_day_for_type(30, post_action_type).each do |date, count|
|
PostAction.count_per_day_for_type(post_action_type).each do |date, count|
|
||||||
report.data << {x: date, y: count}
|
report.data << { x: date, y: count }
|
||||||
end
|
end
|
||||||
query = PostAction.unscoped.where(post_action_type_id: post_action_type)
|
query = PostAction.unscoped.where(post_action_type_id: post_action_type)
|
||||||
report.total = query.count
|
report.total = query.count
|
||||||
|
@ -477,7 +477,6 @@ class Topic < ActiveRecord::Base
|
|||||||
topic_id: self.id)
|
topic_id: self.id)
|
||||||
new_post = creator.create
|
new_post = creator.create
|
||||||
increment!(:moderator_posts_count)
|
increment!(:moderator_posts_count)
|
||||||
new_post
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if new_post.present?
|
if new_post.present?
|
||||||
|
@ -182,9 +182,12 @@ class User < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def created_topic_count
|
def created_topic_count
|
||||||
topics.count
|
stat = user_stat || create_user_stat
|
||||||
|
stat.topic_count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias_method :topic_count, :created_topic_count
|
||||||
|
|
||||||
# tricky, we need our bus to be subscribed from the right spot
|
# tricky, we need our bus to be subscribed from the right spot
|
||||||
def sync_notification_channel_position
|
def sync_notification_channel_position
|
||||||
@unread_notifications_by_type = nil
|
@unread_notifications_by_type = nil
|
||||||
@ -370,11 +373,8 @@ class User < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def post_count
|
def post_count
|
||||||
posts.count
|
stat = user_stat || create_user_stat
|
||||||
end
|
stat.post_count
|
||||||
|
|
||||||
def first_post
|
|
||||||
posts.order('created_at ASC').first
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def flags_given_count
|
def flags_given_count
|
||||||
@ -607,6 +607,10 @@ class User < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def first_post_created_at
|
||||||
|
user_stat.try(:first_post_created_at)
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def badge_grant
|
def badge_grant
|
||||||
|
10
app/serializers/flagged_topic_serializer.rb
Normal file
10
app/serializers/flagged_topic_serializer.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class FlaggedTopicSerializer < ActiveModel::Serializer
|
||||||
|
attributes :id,
|
||||||
|
:title,
|
||||||
|
:slug,
|
||||||
|
:archived,
|
||||||
|
:closed,
|
||||||
|
:visible,
|
||||||
|
:archetype,
|
||||||
|
:relative_url
|
||||||
|
end
|
21
app/serializers/flagged_user_serializer.rb
Normal file
21
app/serializers/flagged_user_serializer.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
class FlaggedUserSerializer < BasicUserSerializer
|
||||||
|
attributes :can_delete_all_posts,
|
||||||
|
:can_be_deleted,
|
||||||
|
:post_count,
|
||||||
|
:topic_count,
|
||||||
|
:email,
|
||||||
|
:ip_address
|
||||||
|
|
||||||
|
def can_delete_all_posts
|
||||||
|
scope.can_delete_all_posts?(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_be_deleted
|
||||||
|
scope.can_delete_user?(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ip_address
|
||||||
|
object.ip_address.try(:to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -164,7 +164,7 @@ class PostSerializer < BasicPostSerializer
|
|||||||
|
|
||||||
# The following only applies if you're logged in
|
# The following only applies if you're logged in
|
||||||
if action_summary[:can_act] && scope.current_user.present?
|
if action_summary[:can_act] && scope.current_user.present?
|
||||||
action_summary[:can_clear_flags] = scope.is_staff? && PostActionType.flag_types.values.include?(id)
|
action_summary[:can_defer_flags] = scope.is_staff? && PostActionType.flag_types.values.include?(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
if post_actions.present? && post_actions.has_key?(id)
|
if post_actions.present? && post_actions.has_key?(id)
|
||||||
|
@ -173,7 +173,7 @@ class TopicViewSerializer < ApplicationSerializer
|
|||||||
count: 0,
|
count: 0,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
can_act: scope.post_can_act?(post, sym)}
|
can_act: scope.post_can_act?(post, sym)}
|
||||||
# TODO: other keys? :can_clear_flags, :acted, :can_undo
|
# TODO: other keys? :can_defer_flags, :acted, :can_undo
|
||||||
end
|
end
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
@ -1044,9 +1044,9 @@ en:
|
|||||||
|
|
||||||
actions:
|
actions:
|
||||||
flag: 'Flag'
|
flag: 'Flag'
|
||||||
clear_flags:
|
defer_flags:
|
||||||
one: "Clear flag"
|
one: "Defer flag"
|
||||||
other: "Clear flags"
|
other: "Defer flags"
|
||||||
it_too:
|
it_too:
|
||||||
off_topic: "Flag it too"
|
off_topic: "Flag it too"
|
||||||
spam: "Flag it too"
|
spam: "Flag it too"
|
||||||
@ -1411,25 +1411,37 @@ en:
|
|||||||
old: "Old"
|
old: "Old"
|
||||||
active: "Active"
|
active: "Active"
|
||||||
|
|
||||||
agree_hide: "Agree (hide post + send PM)"
|
agree_flag_hide_post: "Agree (hide post + send PM)"
|
||||||
agree_hide_title: "Hide this post and automatically send the user a private message urging them to edit it"
|
agree_flag_hide_post_title: "Hide this post and automatically send the user a private message urging them to edit it"
|
||||||
defer: "Defer"
|
defer_flag: "Defer"
|
||||||
defer_title: "No action is necessary at this time, defer any action on this flag until a later date, or never"
|
defer_flag_title: "No action is necessary at this time, defer any action on this flag until a later date, or never"
|
||||||
delete_post: "Delete Post"
|
delete: "Delete"
|
||||||
delete_post_title: "Delete post; if the first post, delete the topic"
|
delete_title: "Delete"
|
||||||
disagree_unhide: "Disagree (unhide post)"
|
delete_post_defer_flag: "Delete Post and defer flag"
|
||||||
disagree_unhide_title: "Remove any flags from this post and make the post visible again"
|
delete_post_defer_flag_title: "Delete post; if the first post, delete the topic"
|
||||||
disagree: "Disagree"
|
delete_post_agree_flag: "Delete Post and agree with flag"
|
||||||
disagree_title: "Disagree with flag, remove any flags from this post"
|
delete_post_agree_flag_title: "Delete post; if the first post, delete the topic"
|
||||||
|
delete_flag_modal_title: "Choose the delete action"
|
||||||
|
delete_spammer: "Delete Spammer"
|
||||||
delete_spammer_title: "Delete the user and all its posts and topics."
|
delete_spammer_title: "Delete the user and all its posts and topics."
|
||||||
|
disagree_flag_unhide_post: "Disagree (unhide post)"
|
||||||
|
disagree_flag_unhide_post_title: "Remove any flags from this post and make the post visible again"
|
||||||
|
disagree_flag: "Disagree"
|
||||||
|
disagree_flag_title: "Disagree with flag, remove any flags from this post"
|
||||||
clear_topic_flags: "Done"
|
clear_topic_flags: "Done"
|
||||||
clear_topic_flags_title: "The topic has been investigated and issues have been resolved. Click Done to remove the flags."
|
clear_topic_flags_title: "The topic has been investigated and issues have been resolved. Click Done to remove the flags."
|
||||||
|
more: "(more...)"
|
||||||
|
|
||||||
|
dispositions:
|
||||||
|
agreed: "agreed"
|
||||||
|
disagreed: "disagreed"
|
||||||
|
defered: "defered"
|
||||||
|
|
||||||
flagged_by: "Flagged by"
|
flagged_by: "Flagged by"
|
||||||
resolved_by: "Resolved by"
|
resolved_by: "Resolved by"
|
||||||
system: "System"
|
system: "System"
|
||||||
error: "Something went wrong"
|
error: "Something went wrong"
|
||||||
view_message: "Reply"
|
reply_message: "Reply"
|
||||||
no_results: "There are no flags."
|
no_results: "There are no flags."
|
||||||
topic_flagged: "This <strong>topic</strong> has been flagged."
|
topic_flagged: "This <strong>topic</strong> has been flagged."
|
||||||
visit_topic: "Visit the topic to take action"
|
visit_topic: "Visit the topic to take action"
|
||||||
|
@ -1211,6 +1211,13 @@ en:
|
|||||||
spam: "Your post was flagged as **spam**: the community thinks it is an advertisement, not useful or relevant to the topic, but promotional in nature."
|
spam: "Your post was flagged as **spam**: the community thinks it is an advertisement, not useful or relevant to the topic, but promotional in nature."
|
||||||
notify_moderators: "Your post was flagged **for moderator attention**: the community thinks something about the post requires moderator intervention."
|
notify_moderators: "Your post was flagged **for moderator attention**: the community thinks something about the post requires moderator intervention."
|
||||||
|
|
||||||
|
flags_dispositions:
|
||||||
|
agreed: "Thanks for your reporting this post. We've agreed with your flag."
|
||||||
|
agreed_and_deleted: "Thanks for your reporting this post. We've agreed with your flag and deleted the post."
|
||||||
|
disagreed: "Thanks for your reporting this post. Unfortunately, we've agreed with your flag."
|
||||||
|
defered: "Thanks for your reporting this post. We're looking into handling this post."
|
||||||
|
defered_and_deleted: "Thanks for your reporting this post. We've agreed with your flag and deleted the post."
|
||||||
|
|
||||||
system_messages:
|
system_messages:
|
||||||
post_hidden:
|
post_hidden:
|
||||||
subject_template: "Post hidden due to community flagging"
|
subject_template: "Post hidden due to community flagging"
|
||||||
|
@ -273,7 +273,7 @@ Discourse::Application.routes.draw do
|
|||||||
resources :post_actions do
|
resources :post_actions do
|
||||||
collection do
|
collection do
|
||||||
get "users"
|
get "users"
|
||||||
post "clear_flags"
|
post "defer_flags"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :user_actions
|
resources :user_actions
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
class AddAgreedAtAndAgreedByIdToPostAction < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :post_actions, :agreed_at, :datetime
|
||||||
|
add_column :post_actions, :agreed_by_id, :integer
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,17 @@
|
|||||||
|
class RenameDeferColumnsOnPostAction < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
rename_column :post_actions, :defer_by, :defered_by_id
|
||||||
|
|
||||||
|
add_column :post_actions, :defered_at, :datetime
|
||||||
|
execute "UPDATE post_actions SET defered_at = updated_at WHERE defer = 't'"
|
||||||
|
remove_column :post_actions, :defer
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
rename_column :post_actions, :defered_by_id, :defer_by
|
||||||
|
|
||||||
|
add_column :post_actions, :defer, :boolean
|
||||||
|
execute "UPDATE post_actions SET defer = 't' WHERE defered_at IS NOT NULL"
|
||||||
|
remove_column :post_actions, :defered_at
|
||||||
|
end
|
||||||
|
end
|
16
db/migrate/20140725172830_remove_message_from_post_action.rb
Normal file
16
db/migrate/20140725172830_remove_message_from_post_action.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
class RemoveMessageFromPostAction < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
remove_column :post_actions, :message
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_column :post_actions, :message, :text
|
||||||
|
|
||||||
|
execute "UPDATE post_actions
|
||||||
|
SET message = p.raw
|
||||||
|
FROM post_actions pa
|
||||||
|
LEFT JOIN posts p ON p.id = pa.related_post_id
|
||||||
|
WHERE post_actions.id = pa.id
|
||||||
|
AND pa.related_post_id IS NOT NULL;"
|
||||||
|
end
|
||||||
|
end
|
6
db/migrate/20140728120708_fix_index_on_post_action.rb
Normal file
6
db/migrate/20140728120708_fix_index_on_post_action.rb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class FixIndexOnPostAction < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
remove_index "post_actions", name: "idx_unique_actions"
|
||||||
|
add_index "post_actions", ["user_id", "post_action_type_id", "post_id", "deleted_at", "targets_topic"], name: "idx_unique_actions", unique: true
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,24 @@
|
|||||||
|
class AddFirstPostCreatedAtToUserStat < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
add_column :user_stats, :first_post_created_at, :datetime
|
||||||
|
|
||||||
|
execute <<-SQL
|
||||||
|
WITH first_posts AS (
|
||||||
|
SELECT p.id,
|
||||||
|
p.user_id,
|
||||||
|
p.created_at,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY p.created_at ASC) AS row
|
||||||
|
FROM posts p
|
||||||
|
)
|
||||||
|
UPDATE user_stats us
|
||||||
|
SET first_post_created_at = fp.created_at
|
||||||
|
FROM first_posts fp
|
||||||
|
WHERE fp.row = 1
|
||||||
|
AND fp.user_id = us.user_id
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :user_stats, :first_post_created_at
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,25 @@
|
|||||||
|
class AddPostAndTopicCountsToUserStat < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
add_column :user_stats, :post_count, :integer, default: 0, null: false
|
||||||
|
add_column :user_stats, :topic_count, :integer, default: 0, null: false
|
||||||
|
|
||||||
|
execute <<-SQL
|
||||||
|
UPDATE user_stats
|
||||||
|
SET post_count = pc.count
|
||||||
|
FROM (SELECT user_id, COUNT(*) AS count FROM posts GROUP BY user_id) AS pc
|
||||||
|
WHERE pc.user_id = user_stats.user_id
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute <<-SQL
|
||||||
|
UPDATE user_stats
|
||||||
|
SET topic_count = tc.count
|
||||||
|
FROM (SELECT user_id, COUNT(*) AS count FROM topics GROUP BY user_id) AS tc
|
||||||
|
WHERE tc.user_id = user_stats.user_id
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :user_stats, :post_count
|
||||||
|
remove_column :user_stats, :topic_count
|
||||||
|
end
|
||||||
|
end
|
@ -1,105 +1,139 @@
|
|||||||
module FlagQuery
|
module FlagQuery
|
||||||
def self.flagged_posts_report(current_user, filter, offset = 0, per_page = 25)
|
|
||||||
|
|
||||||
|
def self.flagged_posts_report(current_user, filter, offset=0, per_page=25)
|
||||||
actions = flagged_post_actions(filter)
|
actions = flagged_post_actions(filter)
|
||||||
|
|
||||||
guardian = Guardian.new(current_user)
|
guardian = Guardian.new(current_user)
|
||||||
|
|
||||||
if !guardian.is_admin?
|
if !guardian.is_admin?
|
||||||
actions = actions.joins(:post => :topic)
|
actions = actions.where('category_id in (?)', guardian.allowed_category_ids)
|
||||||
.where('category_id in (?)', guardian.allowed_category_ids)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
post_ids = actions
|
post_ids = actions.limit(per_page)
|
||||||
.limit(per_page)
|
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.group(:post_id)
|
.group(:post_id)
|
||||||
.order('min(post_actions.created_at) DESC')
|
.order('min(post_actions.created_at) DESC')
|
||||||
.pluck(:post_id).uniq
|
.pluck(:post_id)
|
||||||
|
.uniq
|
||||||
|
|
||||||
return nil if post_ids.blank?
|
return nil if post_ids.blank?
|
||||||
|
|
||||||
actions = actions
|
posts = SqlBuilder.new("
|
||||||
.order('post_actions.created_at DESC')
|
SELECT p.id,
|
||||||
.includes({:related_post => :topic})
|
p.cooked,
|
||||||
|
p.user_id,
|
||||||
posts = SqlBuilder.new("SELECT p.id, t.title, p.cooked, p.user_id,
|
p.topic_id,
|
||||||
p.topic_id, p.post_number, p.hidden, t.visible topic_visible,
|
p.post_number,
|
||||||
p.deleted_at, t.deleted_at topic_deleted_at
|
p.hidden,
|
||||||
|
p.deleted_at
|
||||||
FROM posts p
|
FROM posts p
|
||||||
JOIN topics t ON t.id = p.topic_id
|
|
||||||
WHERE p.id in (:post_ids)").map_exec(OpenStruct, post_ids: post_ids)
|
WHERE p.id in (:post_ids)").map_exec(OpenStruct, post_ids: post_ids)
|
||||||
|
|
||||||
post_lookup = {}
|
post_lookup = {}
|
||||||
users = Set.new
|
user_ids = Set.new
|
||||||
|
topic_ids = Set.new
|
||||||
|
|
||||||
posts.each do |p|
|
posts.each do |p|
|
||||||
users << p.user_id
|
user_ids << p.user_id
|
||||||
|
topic_ids << p.topic_id
|
||||||
p.excerpt = Post.excerpt(p.cooked)
|
p.excerpt = Post.excerpt(p.cooked)
|
||||||
p.topic_slug = Slug.for(p.title)
|
p.delete_field(:cooked)
|
||||||
post_lookup[p.id] = p
|
post_lookup[p.id] = p
|
||||||
end
|
end
|
||||||
|
|
||||||
# maintain order
|
post_actions = actions.order('post_actions.created_at DESC')
|
||||||
posts = post_ids.map{|id| post_lookup[id]}
|
.includes(related_post: { topic: { posts: :user }})
|
||||||
|
.where(post_id: post_ids)
|
||||||
post_actions = actions.where(:post_id => post_ids)
|
|
||||||
|
|
||||||
post_actions.each do |pa|
|
post_actions.each do |pa|
|
||||||
post = post_lookup[pa.post_id]
|
post = post_lookup[pa.post_id]
|
||||||
post.post_actions ||= []
|
post.post_actions ||= []
|
||||||
action = pa.attributes
|
# TODO: add serializer so we can skip this
|
||||||
|
action = {
|
||||||
|
id: pa.id,
|
||||||
|
post_id: pa.post_id,
|
||||||
|
user_id: pa.user_id,
|
||||||
|
post_action_type_id: pa.post_action_type_id,
|
||||||
|
created_at: pa.created_at,
|
||||||
|
disposed_by_id: pa.disposed_by_id,
|
||||||
|
disposed_at: pa.disposed_at,
|
||||||
|
disposition: pa.disposition,
|
||||||
|
related_post_id: pa.related_post_id,
|
||||||
|
targets_topic: pa.targets_topic,
|
||||||
|
staff_took_action: pa.staff_took_action
|
||||||
|
}
|
||||||
action[:name_key] = PostActionType.types.key(pa.post_action_type_id)
|
action[:name_key] = PostActionType.types.key(pa.post_action_type_id)
|
||||||
if (pa.related_post && pa.related_post.topic)
|
|
||||||
action.merge!(topic_id: pa.related_post.topic_id,
|
if pa.related_post && pa.related_post.topic
|
||||||
slug: pa.related_post.topic.slug,
|
conversation = {}
|
||||||
permalink: pa.related_post.topic.url)
|
related_topic = pa.related_post.topic
|
||||||
|
if response = related_topic.posts[0]
|
||||||
|
conversation[:response] = {
|
||||||
|
excerpt: excerpt(response.cooked),
|
||||||
|
user_id: response.user_id
|
||||||
|
}
|
||||||
|
user_ids << response.user_id
|
||||||
|
if reply = related_topic.posts[1]
|
||||||
|
conversation[:reply] = {
|
||||||
|
excerpt: excerpt(reply.cooked),
|
||||||
|
user_id: reply.user_id
|
||||||
|
}
|
||||||
|
user_ids << reply.user_id
|
||||||
|
conversation[:has_more] = related_topic.posts_count > 2
|
||||||
end
|
end
|
||||||
post.post_actions << action
|
|
||||||
users << pa.user_id
|
|
||||||
users << pa.deleted_by_id if pa.deleted_by_id
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO add serializer so we can skip this
|
action.merge!(permalink: related_topic.relative_url, conversation: conversation)
|
||||||
|
end
|
||||||
|
|
||||||
|
post.post_actions << action
|
||||||
|
|
||||||
|
user_ids << pa.user_id
|
||||||
|
user_ids << pa.disposed_by_id if pa.disposed_by_id
|
||||||
|
end
|
||||||
|
|
||||||
|
# maintain order
|
||||||
|
posts = post_ids.map { |id| post_lookup[id] }
|
||||||
|
# TODO: add serializer so we can skip this
|
||||||
posts.map!(&:marshal_dump)
|
posts.map!(&:marshal_dump)
|
||||||
[posts, User.where(id: users.to_a).to_a]
|
|
||||||
|
[
|
||||||
|
posts,
|
||||||
|
Topic.with_deleted.where(id: topic_ids.to_a).to_a,
|
||||||
|
User.includes(:user_stat).where(id: user_ids.to_a).to_a
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def self.flagged_post_ids(filter, offset, limit)
|
|
||||||
<<SQL
|
|
||||||
|
|
||||||
SELECT p.id from posts p
|
|
||||||
JOIN topics t ON t.id = p.topic_id
|
|
||||||
WHERE p.id IN (
|
|
||||||
SELECT post_id from post_actions
|
|
||||||
WHERE
|
|
||||||
)
|
|
||||||
/*offset*/
|
|
||||||
/*limit*/
|
|
||||||
|
|
||||||
SQL
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.flagged_post_actions(filter)
|
def self.flagged_post_actions(filter)
|
||||||
post_actions = PostAction
|
post_actions = PostAction.flags
|
||||||
.where(post_action_type_id: PostActionType.notify_flag_type_ids)
|
.joins("INNER JOIN posts ON posts.id = post_actions.post_id")
|
||||||
.joins(:post => :topic)
|
.joins("INNER JOIN topics ON topics.id = posts.topic_id")
|
||||||
|
|
||||||
if filter == 'old'
|
if filter == "old"
|
||||||
post_actions
|
post_actions.with_deleted
|
||||||
.with_deleted
|
.where("post_actions.deleted_at IS NOT NULL OR
|
||||||
.where('post_actions.deleted_at IS NOT NULL OR
|
post_actions.defered_at IS NOT NULL OR
|
||||||
defer = true OR
|
post_actions.agreed_at IS NOT NULL")
|
||||||
topics.deleted_at IS NOT NULL OR
|
|
||||||
posts.deleted_at IS NOT NULL')
|
|
||||||
else
|
else
|
||||||
post_actions
|
post_actions.active
|
||||||
.where('defer IS NULL OR
|
.where("posts.deleted_at" => nil)
|
||||||
defer = false')
|
.where("topics.deleted_at" => nil)
|
||||||
.where('posts.deleted_at IS NULL AND
|
|
||||||
topics.deleted_at IS NULL')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def self.excerpt(cooked)
|
||||||
|
excerpt = Post.excerpt(cooked, 200)
|
||||||
|
# remove the first link if it's the first node
|
||||||
|
fragment = Nokogiri::HTML.fragment(excerpt)
|
||||||
|
if fragment.children.first == fragment.css("a:first").first
|
||||||
|
fragment.children.first.remove
|
||||||
|
end
|
||||||
|
fragment.to_html.strip
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -29,7 +29,7 @@ module PostGuardian
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_clear_flags?(post)
|
def can_defer_flags?(post)
|
||||||
is_staff? && post
|
is_staff? && post
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -54,7 +54,11 @@ module PostGuardian
|
|||||||
end
|
end
|
||||||
|
|
||||||
def can_delete_all_posts?(user)
|
def can_delete_all_posts?(user)
|
||||||
is_staff? && user && !user.admin? && (user.first_post.nil? || user.first_post.created_at >= SiteSetting.delete_user_max_post_age.days.ago) && user.post_count <= SiteSetting.delete_all_posts_max.to_i
|
is_staff? &&
|
||||||
|
user &&
|
||||||
|
!user.admin? &&
|
||||||
|
(user.first_post_created_at.nil? || user.first_post_created_at >= SiteSetting.delete_user_max_post_age.days.ago) &&
|
||||||
|
user.post_count <= SiteSetting.delete_all_posts_max.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
# Creating Method
|
# Creating Method
|
||||||
|
@ -35,12 +35,11 @@ module UserGuardian
|
|||||||
end
|
end
|
||||||
|
|
||||||
def can_delete_user?(user)
|
def can_delete_user?(user)
|
||||||
return false if user.nil?
|
return false if user.nil? || user.admin?
|
||||||
return false if user.admin?
|
|
||||||
if is_me?(user)
|
if is_me?(user)
|
||||||
user.post_count <= 1
|
user.post_count <= 1
|
||||||
else
|
else
|
||||||
is_staff? && (user.first_post.nil? || user.first_post.created_at > SiteSetting.delete_user_max_post_age.to_i.days.ago)
|
is_staff? && (user.first_post_created_at.nil? || user.first_post_created_at > SiteSetting.delete_user_max_post_age.to_i.days.ago)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -61,7 +61,6 @@ class PostCreator
|
|||||||
save_post
|
save_post
|
||||||
extract_links
|
extract_links
|
||||||
store_unique_post_key
|
store_unique_post_key
|
||||||
consider_clearing_flags
|
|
||||||
track_topic
|
track_topic
|
||||||
update_topic_stats
|
update_topic_stats
|
||||||
update_user_counts
|
update_user_counts
|
||||||
@ -147,21 +146,6 @@ class PostCreator
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_possible_flags(topic)
|
|
||||||
# at this point we know the topic is a PM and has been replied to ... check if we need to clear any flags
|
|
||||||
#
|
|
||||||
first_post = Post.select(:id).where(topic_id: topic.id).find_by("post_number = 1")
|
|
||||||
post_action = nil
|
|
||||||
|
|
||||||
if first_post
|
|
||||||
post_action = PostAction.find_by(related_post_id: first_post.id, deleted_at: nil, post_action_type_id: PostActionType.types[:notify_moderators])
|
|
||||||
end
|
|
||||||
|
|
||||||
if post_action
|
|
||||||
post_action.remove_act!(@user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def setup_topic
|
def setup_topic
|
||||||
@ -233,20 +217,23 @@ class PostCreator
|
|||||||
@post.store_unique_post_key
|
@post.store_unique_post_key
|
||||||
end
|
end
|
||||||
|
|
||||||
def consider_clearing_flags
|
def update_user_counts
|
||||||
return if @opts[:import_mode]
|
@user.create_user_stat if @user.user_stat.nil?
|
||||||
return unless @topic.private_message? && @post.post_number > 1 && @topic.user_id != @post.user_id
|
|
||||||
|
|
||||||
clear_possible_flags(@topic)
|
if @user.user_stat.first_post_created_at.nil?
|
||||||
|
@user.user_stat.first_post_created_at = @post.created_at
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_user_counts
|
@user.user_stat.post_count += 1
|
||||||
|
@user.user_stat.topic_count += 1 if @post.post_number == 1
|
||||||
|
|
||||||
# We don't count replies to your own topics
|
# We don't count replies to your own topics
|
||||||
if !@opts[:import_mode] && @user.id != @topic.user_id
|
if !@opts[:import_mode] && @user.id != @topic.user_id
|
||||||
@user.user_stat.update_topic_reply_count
|
@user.user_stat.update_topic_reply_count
|
||||||
@user.user_stat.save!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@user.user_stat.save!
|
||||||
|
|
||||||
@user.last_posted_at = @post.created_at
|
@user.last_posted_at = @post.created_at
|
||||||
@user.save!
|
@user.save!
|
||||||
end
|
end
|
||||||
|
@ -62,7 +62,8 @@ class PostDestroyer
|
|||||||
feature_users_in_the_topic
|
feature_users_in_the_topic
|
||||||
Topic.reset_highest(@post.topic_id)
|
Topic.reset_highest(@post.topic_id)
|
||||||
end
|
end
|
||||||
trash_post_actions
|
trash_public_post_actions
|
||||||
|
agree_with_flags
|
||||||
trash_user_actions
|
trash_user_actions
|
||||||
@post.update_flagged_posts_count
|
@post.update_flagged_posts_count
|
||||||
remove_associated_replies
|
remove_associated_replies
|
||||||
@ -130,13 +131,16 @@ class PostDestroyer
|
|||||||
Jobs.enqueue(:feature_topic_users, topic_id: @post.topic_id, except_post_id: @post.id)
|
Jobs.enqueue(:feature_topic_users, topic_id: @post.topic_id, except_post_id: @post.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def trash_post_actions
|
def trash_public_post_actions
|
||||||
@post.post_actions.each do |pa|
|
public_post_actions = PostAction.publics.where(post_id: @post.id)
|
||||||
pa.trash!(@user)
|
public_post_actions.each { |pa| pa.trash!(@user) }
|
||||||
|
|
||||||
|
f = PostActionType.public_types.map { |k,v| ["#{k}_count", 0] }
|
||||||
|
Post.with_deleted.where(id: @post.id).update_all(Hash[*f.flatten])
|
||||||
end
|
end
|
||||||
|
|
||||||
f = PostActionType.types.map{|k,v| ["#{k}_count", 0]}
|
def agree_with_flags
|
||||||
Post.with_deleted.where(id: @post.id).update_all(Hash[*f.flatten])
|
PostAction.agree_flags!(@post, @user, delete_post: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def trash_user_actions
|
def trash_user_actions
|
||||||
|
@ -29,9 +29,7 @@ class PostJobsEnqueuer
|
|||||||
end
|
end
|
||||||
|
|
||||||
def after_post_create
|
def after_post_create
|
||||||
if @post.post_number > 1
|
TopicTrackingState.publish_unread(@post) if @post.post_number > 1
|
||||||
TopicTrackingState.publish_unread(@post)
|
|
||||||
end
|
|
||||||
|
|
||||||
Jobs.enqueue_in(
|
Jobs.enqueue_in(
|
||||||
SiteSetting.email_time_window_mins.minutes,
|
SiteSetting.email_time_window_mins.minutes,
|
||||||
|
@ -105,7 +105,7 @@ class PostRevisor
|
|||||||
@post.hidden_at = nil
|
@post.hidden_at = nil
|
||||||
@post.topic.update_attributes(visible: true)
|
@post.topic.update_attributes(visible: true)
|
||||||
|
|
||||||
PostAction.clear_flags!(@post, -1)
|
PostAction.clear_flags!(@post, Discourse.system_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
@post.extract_quoted_post_numbers
|
@post.extract_quoted_post_numbers
|
||||||
|
@ -23,17 +23,19 @@ describe FlagQuery do
|
|||||||
PostAction.act(codinghorror, post2, PostActionType.types[:spam])
|
PostAction.act(codinghorror, post2, PostActionType.types[:spam])
|
||||||
PostAction.act(user2, post2, PostActionType.types[:spam])
|
PostAction.act(user2, post2, PostActionType.types[:spam])
|
||||||
|
|
||||||
posts, users = FlagQuery.flagged_posts_report(admin, "")
|
posts, topics, users = FlagQuery.flagged_posts_report(admin, "")
|
||||||
posts.count.should == 2
|
posts.count.should == 2
|
||||||
first = posts.first
|
first = posts.first
|
||||||
|
|
||||||
users.count.should == 5
|
users.count.should == 5
|
||||||
first[:post_actions].count.should == 2
|
first[:post_actions].count.should == 2
|
||||||
|
|
||||||
|
topics.count.should == 2
|
||||||
|
|
||||||
second = posts[1]
|
second = posts[1]
|
||||||
|
|
||||||
second[:post_actions].count.should == 3
|
second[:post_actions].count.should == 3
|
||||||
second[:post_actions].first[:permalink].should == mod_message.related_post.topic.url
|
second[:post_actions].first[:permalink].should == mod_message.related_post.topic.relative_url
|
||||||
|
|
||||||
posts, users = FlagQuery.flagged_posts_report(admin, "", 1)
|
posts, users = FlagQuery.flagged_posts_report(admin, "", 1)
|
||||||
posts.count.should == 1
|
posts.count.should == 1
|
||||||
|
@ -81,25 +81,25 @@ describe Guardian do
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
describe "can_clear_flags" do
|
describe "can_defer_flags" do
|
||||||
let(:post) { Fabricate(:post) }
|
let(:post) { Fabricate(:post) }
|
||||||
let(:user) { post.user }
|
let(:user) { post.user }
|
||||||
let(:moderator) { Fabricate(:moderator) }
|
let(:moderator) { Fabricate(:moderator) }
|
||||||
|
|
||||||
it "returns false when the user is nil" do
|
it "returns false when the user is nil" do
|
||||||
Guardian.new(nil).can_clear_flags?(post).should be_false
|
Guardian.new(nil).can_defer_flags?(post).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns false when the post is nil" do
|
it "returns false when the post is nil" do
|
||||||
Guardian.new(moderator).can_clear_flags?(nil).should be_false
|
Guardian.new(moderator).can_defer_flags?(nil).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns false when the user is not a moderator" do
|
it "returns false when the user is not a moderator" do
|
||||||
Guardian.new(user).can_clear_flags?(post).should be_false
|
Guardian.new(user).can_defer_flags?(post).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns true when the user is a moderator" do
|
it "returns true when the user is a moderator" do
|
||||||
Guardian.new(moderator).can_clear_flags?(post).should be_true
|
Guardian.new(moderator).can_defer_flags?(post).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@ -1350,7 +1350,7 @@ describe Guardian do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "delete myself" do
|
context "delete myself" do
|
||||||
let(:myself) { Fabricate.build(:user, created_at: 6.months.ago) }
|
let(:myself) { Fabricate(:user, created_at: 6.months.ago) }
|
||||||
subject { Guardian.new(myself).can_delete_user?(myself) }
|
subject { Guardian.new(myself).can_delete_user?(myself) }
|
||||||
|
|
||||||
it "is true to delete myself and I have never made a post" do
|
it "is true to delete myself and I have never made a post" do
|
||||||
@ -1375,7 +1375,7 @@ describe Guardian do
|
|||||||
|
|
||||||
it "is true if user is not an admin and first post is not too old" do
|
it "is true if user is not an admin and first post is not too old" do
|
||||||
user = Fabricate.build(:user, created_at: 100.days.ago)
|
user = Fabricate.build(:user, created_at: 100.days.ago)
|
||||||
user.stubs(:first_post).returns(Fabricate.build(:post, created_at: 9.days.ago))
|
user.stubs(:first_post_created_at).returns(9.days.ago)
|
||||||
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
||||||
Guardian.new(actor).can_delete_user?(user).should == true
|
Guardian.new(actor).can_delete_user?(user).should == true
|
||||||
end
|
end
|
||||||
@ -1386,7 +1386,7 @@ describe Guardian do
|
|||||||
|
|
||||||
it "is false if user's first post is too old" do
|
it "is false if user's first post is too old" do
|
||||||
user = Fabricate.build(:user, created_at: 100.days.ago)
|
user = Fabricate.build(:user, created_at: 100.days.ago)
|
||||||
user.stubs(:first_post).returns(Fabricate.build(:post, created_at: 11.days.ago))
|
user.stubs(:first_post_created_at).returns(11.days.ago)
|
||||||
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
||||||
Guardian.new(actor).can_delete_user?(user).should == false
|
Guardian.new(actor).can_delete_user?(user).should == false
|
||||||
end
|
end
|
||||||
@ -1419,19 +1419,19 @@ describe Guardian do
|
|||||||
shared_examples "can_delete_all_posts examples" do
|
shared_examples "can_delete_all_posts examples" do
|
||||||
it "is true if user has no posts" do
|
it "is true if user has no posts" do
|
||||||
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
||||||
Guardian.new(actor).can_delete_all_posts?(Fabricate.build(:user, created_at: 100.days.ago)).should be_true
|
Guardian.new(actor).can_delete_all_posts?(Fabricate(:user, created_at: 100.days.ago)).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is true if user's first post is newer than delete_user_max_post_age days old" do
|
it "is true if user's first post is newer than delete_user_max_post_age days old" do
|
||||||
user = Fabricate.build(:user, created_at: 100.days.ago)
|
user = Fabricate(:user, created_at: 100.days.ago)
|
||||||
user.stubs(:first_post).returns(Fabricate.build(:post, created_at: 9.days.ago))
|
user.stubs(:first_post_created_at).returns(9.days.ago)
|
||||||
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
||||||
Guardian.new(actor).can_delete_all_posts?(user).should be_true
|
Guardian.new(actor).can_delete_all_posts?(user).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is false if user's first post is older than delete_user_max_post_age days old" do
|
it "is false if user's first post is older than delete_user_max_post_age days old" do
|
||||||
user = Fabricate.build(:user, created_at: 100.days.ago)
|
user = Fabricate(:user, created_at: 100.days.ago)
|
||||||
user.stubs(:first_post).returns(Fabricate.build(:post, created_at: 11.days.ago))
|
user.stubs(:first_post_created_at).returns(11.days.ago)
|
||||||
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
||||||
Guardian.new(actor).can_delete_all_posts?(user).should be_false
|
Guardian.new(actor).can_delete_all_posts?(user).should be_false
|
||||||
end
|
end
|
||||||
@ -1441,14 +1441,14 @@ describe Guardian do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "is true if number of posts is small" do
|
it "is true if number of posts is small" do
|
||||||
u = Fabricate.build(:user, created_at: 1.day.ago)
|
u = Fabricate(:user, created_at: 1.day.ago)
|
||||||
u.stubs(:post_count).returns(1)
|
u.stubs(:post_count).returns(1)
|
||||||
SiteSetting.stubs(:delete_all_posts_max).returns(10)
|
SiteSetting.stubs(:delete_all_posts_max).returns(10)
|
||||||
Guardian.new(actor).can_delete_all_posts?(u).should be_true
|
Guardian.new(actor).can_delete_all_posts?(u).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is false if number of posts is not small" do
|
it "is false if number of posts is not small" do
|
||||||
u = Fabricate.build(:user, created_at: 1.day.ago)
|
u = Fabricate(:user, created_at: 1.day.ago)
|
||||||
u.stubs(:post_count).returns(11)
|
u.stubs(:post_count).returns(11)
|
||||||
SiteSetting.stubs(:delete_all_posts_max).returns(10)
|
SiteSetting.stubs(:delete_all_posts_max).returns(10)
|
||||||
Guardian.new(actor).can_delete_all_posts?(u).should be_false
|
Guardian.new(actor).can_delete_all_posts?(u).should be_false
|
||||||
@ -1528,7 +1528,7 @@ describe Guardian do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'for a new user' do
|
context 'for a new user' do
|
||||||
let(:target_user) { build(:user, created_at: 1.minute.ago) }
|
let(:target_user) { Fabricate(:user, created_at: 1.minute.ago) }
|
||||||
include_examples "staff can always change usernames"
|
include_examples "staff can always change usernames"
|
||||||
|
|
||||||
it "is true for the user to change their own username" do
|
it "is true for the user to change their own username" do
|
||||||
@ -1541,7 +1541,7 @@ describe Guardian do
|
|||||||
SiteSetting.stubs(:username_change_period).returns(3)
|
SiteSetting.stubs(:username_change_period).returns(3)
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:target_user) { build(:user, created_at: 4.days.ago) }
|
let(:target_user) { Fabricate(:user, created_at: 4.days.ago) }
|
||||||
|
|
||||||
context 'with no posts' do
|
context 'with no posts' do
|
||||||
include_examples "staff can always change usernames"
|
include_examples "staff can always change usernames"
|
||||||
|
@ -263,28 +263,24 @@ describe PostDestroyer do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "post actions" do
|
describe "post actions" do
|
||||||
let(:codinghorror) { Fabricate(:coding_horror) }
|
|
||||||
let(:bookmark) { PostAction.new(user_id: post.user_id, post_action_type_id: PostActionType.types[:bookmark] , post_id: post.id) }
|
|
||||||
let(:second_post) { Fabricate(:post, topic_id: post.topic_id) }
|
let(:second_post) { Fabricate(:post, topic_id: post.topic_id) }
|
||||||
|
let!(:bookmark) { PostAction.act(moderator, second_post, PostActionType.types[:bookmark]) }
|
||||||
|
let!(:flag) { PostAction.act(moderator, second_post, PostActionType.types[:off_topic]) }
|
||||||
|
|
||||||
it "should reset counts when a post is deleted" do
|
it "should delete public post actions and agree with flags" do
|
||||||
PostAction.act(codinghorror, second_post, PostActionType.types[:off_topic])
|
second_post.expects(:update_flagged_posts_count)
|
||||||
expect { PostDestroyer.new(moderator, second_post).destroy }.to change(PostAction, :flagged_posts_count).by(-1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should delete the post actions" do
|
|
||||||
flag = PostAction.act(codinghorror, second_post, PostActionType.types[:off_topic])
|
|
||||||
PostDestroyer.new(moderator, second_post).destroy
|
PostDestroyer.new(moderator, second_post).destroy
|
||||||
expect(PostAction.find_by(id: flag.id)).to be_nil
|
|
||||||
expect(PostAction.find_by(id: bookmark.id)).to be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should update flag counts on the post' do
|
PostAction.find_by(id: bookmark.id).should == nil
|
||||||
PostAction.act(codinghorror, second_post, PostActionType.types[:off_topic])
|
|
||||||
PostDestroyer.new(moderator, second_post.reload).destroy
|
off_topic = PostAction.find_by(id: flag.id)
|
||||||
|
off_topic.should_not == nil
|
||||||
|
off_topic.agreed_at.should_not == nil
|
||||||
|
|
||||||
second_post.reload
|
second_post.reload
|
||||||
expect(second_post.off_topic_count).to eq(0)
|
second_post.bookmark_count.should == 0
|
||||||
expect(second_post.bookmark_count).to eq(0)
|
second_post.off_topic_count.should == 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -307,19 +307,28 @@ describe Admin::UsersController do
|
|||||||
response.should be_forbidden
|
response.should be_forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns an error if the user has posts" do
|
context "user has post" do
|
||||||
Fabricate(:post, user: @delete_me)
|
|
||||||
|
before do
|
||||||
|
@user = build(:user)
|
||||||
|
@user.stubs(:post_count).returns(1)
|
||||||
|
@user.stubs(:first_post_created_at).returns(Time.zone.now)
|
||||||
|
User.expects(:find_by).with(id: @delete_me.id).returns(@user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error" do
|
||||||
xhr :delete, :destroy, id: @delete_me.id
|
xhr :delete, :destroy, id: @delete_me.id
|
||||||
response.should be_forbidden
|
response.should be_forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't return an error if the user has posts and delete_posts == true" do
|
it "doesn't return an error if delete_posts == true" do
|
||||||
Fabricate(:post, user: @delete_me)
|
UserDestroyer.any_instance.expects(:destroy).with(@user, has_entry('delete_posts' => true)).returns(true)
|
||||||
UserDestroyer.any_instance.expects(:destroy).with(@delete_me, has_entry('delete_posts' => true)).returns(true)
|
|
||||||
xhr :delete, :destroy, id: @delete_me.id, delete_posts: true
|
xhr :delete, :destroy, id: @delete_me.id, delete_posts: true
|
||||||
response.should be_success
|
response.should be_success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
it "deletes the user record" do
|
it "deletes the user record" do
|
||||||
UserDestroyer.any_instance.expects(:destroy).returns(true)
|
UserDestroyer.any_instance.expects(:destroy).returns(true)
|
||||||
xhr :delete, :destroy, id: @delete_me.id
|
xhr :delete, :destroy, id: @delete_me.id
|
||||||
|
@ -102,13 +102,13 @@ describe PostActionsController do
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'clear_flags' do
|
context 'defer_flags' do
|
||||||
|
|
||||||
let(:flagged_post) { Fabricate(:post, user: Fabricate(:coding_horror)) }
|
let(:flagged_post) { Fabricate(:post, user: Fabricate(:coding_horror)) }
|
||||||
|
|
||||||
context "not logged in" do
|
context "not logged in" do
|
||||||
it "should not allow them to clear flags" do
|
it "should not allow them to clear flags" do
|
||||||
lambda { xhr :post, :clear_flags }.should raise_error(Discourse::NotLoggedIn)
|
lambda { xhr :post, :defer_flags }.should raise_error(Discourse::NotLoggedIn)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -116,43 +116,38 @@ describe PostActionsController do
|
|||||||
let!(:user) { log_in(:moderator) }
|
let!(:user) { log_in(:moderator) }
|
||||||
|
|
||||||
it "raises an error without a post_action_type_id" do
|
it "raises an error without a post_action_type_id" do
|
||||||
-> { xhr :post, :clear_flags, id: flagged_post.id }.should raise_error(ActionController::ParameterMissing)
|
-> { xhr :post, :defer_flags, id: flagged_post.id }.should raise_error(ActionController::ParameterMissing)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "raises an error when the user doesn't have access" do
|
it "raises an error when the user doesn't have access" do
|
||||||
Guardian.any_instance.expects(:can_clear_flags?).returns(false)
|
Guardian.any_instance.expects(:can_defer_flags?).returns(false)
|
||||||
xhr :post, :clear_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
xhr :post, :defer_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
||||||
response.should be_forbidden
|
response.should be_forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
context "success" do
|
context "success" do
|
||||||
before do
|
before do
|
||||||
Guardian.any_instance.expects(:can_clear_flags?).returns(true)
|
Guardian.any_instance.expects(:can_defer_flags?).returns(true)
|
||||||
PostAction.expects(:clear_flags!).with(flagged_post, user.id, PostActionType.types[:spam])
|
PostAction.expects(:defer_flags!).with(flagged_post, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "delegates to clear_flags" do
|
it "delegates to defer_flags" do
|
||||||
xhr :post, :clear_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
xhr :post, :defer_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
||||||
response.should be_success
|
response.should be_success
|
||||||
end
|
end
|
||||||
|
|
||||||
it "works with a deleted post" do
|
it "works with a deleted post" do
|
||||||
flagged_post.trash!(user)
|
flagged_post.trash!(user)
|
||||||
xhr :post, :clear_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
xhr :post, :defer_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
||||||
response.should be_success
|
response.should be_success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe 'users' do
|
describe 'users' do
|
||||||
|
|
||||||
let!(:post) { Fabricate(:post, user: log_in) }
|
let!(:post) { Fabricate(:post, user: log_in) }
|
||||||
@ -188,6 +183,4 @@ describe PostActionsController do
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -12,7 +12,6 @@ describe PostAction do
|
|||||||
let(:post) { Fabricate(:post) }
|
let(:post) { Fabricate(:post) }
|
||||||
let(:bookmark) { PostAction.new(user_id: post.user_id, post_action_type_id: PostActionType.types[:bookmark] , post_id: post.id) }
|
let(:bookmark) { PostAction.new(user_id: post.user_id, post_action_type_id: PostActionType.types[:bookmark] , post_id: post.id) }
|
||||||
|
|
||||||
|
|
||||||
describe "messaging" do
|
describe "messaging" do
|
||||||
|
|
||||||
it "notify moderators integration test" do
|
it "notify moderators integration test" do
|
||||||
@ -41,13 +40,12 @@ describe PostAction do
|
|||||||
# Notification level should be "Watching" for everyone
|
# Notification level should be "Watching" for everyone
|
||||||
topic.topic_users(true).map(&:notification_level).uniq.should == [TopicUser.notification_levels[:watching]]
|
topic.topic_users(true).map(&:notification_level).uniq.should == [TopicUser.notification_levels[:watching]]
|
||||||
|
|
||||||
# reply to PM should clear flag
|
# reply to PM should not clear flag
|
||||||
p = PostCreator.new(mod, topic_id: posts[0].topic_id, raw: "This is my test reply to the user, it should clear flags")
|
p = PostCreator.new(mod, topic_id: posts[0].topic_id, raw: "This is my test reply to the user, it should clear flags")
|
||||||
p.create
|
p.create
|
||||||
|
|
||||||
action.reload
|
action.reload
|
||||||
action.deleted_at.should_not be_nil
|
action.deleted_at.should be_nil
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'notify_moderators' do
|
describe 'notify_moderators' do
|
||||||
@ -87,7 +85,7 @@ describe PostAction do
|
|||||||
PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
|
PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
|
||||||
PostAction.flagged_posts_count.should == 1
|
PostAction.flagged_posts_count.should == 1
|
||||||
|
|
||||||
PostAction.clear_flags!(post, -1)
|
PostAction.clear_flags!(post, Discourse.system_user)
|
||||||
PostAction.flagged_posts_count.should == 0
|
PostAction.flagged_posts_count.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -103,7 +101,7 @@ describe PostAction do
|
|||||||
PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
|
PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
|
||||||
post.hidden.should be_false
|
post.hidden.should be_false
|
||||||
post.hidden_at.should be_blank
|
post.hidden_at.should be_blank
|
||||||
PostAction.defer_flags!(post, admin.id)
|
PostAction.defer_flags!(post, admin)
|
||||||
PostAction.flagged_posts_count.should == 0
|
PostAction.flagged_posts_count.should == 0
|
||||||
post.reload
|
post.reload
|
||||||
post.hidden.should be_false
|
post.hidden.should be_false
|
||||||
@ -220,7 +218,7 @@ describe PostAction do
|
|||||||
|
|
||||||
# If staff takes action, it is ranked higher
|
# If staff takes action, it is ranked higher
|
||||||
admin = Fabricate(:admin)
|
admin = Fabricate(:admin)
|
||||||
pa = PostAction.act(admin, post, PostActionType.types[:spam], take_action: true)
|
PostAction.act(admin, post, PostActionType.types[:spam], take_action: true)
|
||||||
PostAction.flag_counts_for(post.id).should == [0, 8]
|
PostAction.flag_counts_for(post.id).should == [0, 8]
|
||||||
|
|
||||||
# If a flag is dismissed
|
# If a flag is dismissed
|
||||||
@ -252,7 +250,7 @@ describe PostAction do
|
|||||||
post.reload
|
post.reload
|
||||||
post.spam_count.should == 1
|
post.spam_count.should == 1
|
||||||
|
|
||||||
PostAction.clear_flags!(post, -1)
|
PostAction.clear_flags!(post, Discourse.system_user)
|
||||||
post.reload
|
post.reload
|
||||||
|
|
||||||
post.spam_count.should == 0
|
post.spam_count.should == 0
|
||||||
|
@ -764,10 +764,6 @@ describe Topic do
|
|||||||
topic.moderator_posts_count.should == 0
|
topic.moderator_posts_count.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "its user has a topics_count of 1" do
|
|
||||||
topic.user.created_topic_count.should == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'post' do
|
context 'post' do
|
||||||
let(:post) { Fabricate(:post, topic: topic, user: topic.user) }
|
let(:post) { Fabricate(:post, topic: topic, user: topic.user) }
|
||||||
|
|
||||||
|
@ -81,6 +81,10 @@ describe UserDestroyer do
|
|||||||
|
|
||||||
context "delete_posts is false" do
|
context "delete_posts is false" do
|
||||||
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user) }
|
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user) }
|
||||||
|
before do
|
||||||
|
@user.stubs(:post_count).returns(1)
|
||||||
|
@user.stubs(:first_post_created_at).returns(Time.zone.now)
|
||||||
|
end
|
||||||
|
|
||||||
it 'should not delete the user' do
|
it 'should not delete the user' do
|
||||||
expect { destroy rescue nil }.to_not change { User.count }
|
expect { destroy rescue nil }.to_not change { User.count }
|
||||||
|
Reference in New Issue
Block a user