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:
parent
717f57c968
commit
bddffa7f9a
|
@ -8,36 +8,34 @@
|
|||
**/
|
||||
export default Ember.ArrayController.extend({
|
||||
|
||||
adminOldFlagsView: Em.computed.equal('query', 'old'),
|
||||
adminActiveFlagsView: Em.computed.equal('query', 'active'),
|
||||
|
||||
actions: {
|
||||
/**
|
||||
Clear all flags on a post
|
||||
|
||||
@method clearFlags
|
||||
@param {Discourse.FlaggedPost} item The post whose flags we want to clear
|
||||
**/
|
||||
disagreeFlags: function(item) {
|
||||
var adminFlagsController = this;
|
||||
item.disagreeFlags().then(function() {
|
||||
adminFlagsController.removeObject(item);
|
||||
}, function() {
|
||||
agreeFlags: function (flaggedPost) {
|
||||
var self = this;
|
||||
flaggedPost.agreeFlags().then(function () {
|
||||
self.removeObject(flaggedPost);
|
||||
}, function () {
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
},
|
||||
|
||||
agreeFlags: function(item) {
|
||||
var adminFlagsController = this;
|
||||
item.agreeFlags().then(function() {
|
||||
adminFlagsController.removeObject(item);
|
||||
}, function() {
|
||||
disagreeFlags: function (flaggedPost) {
|
||||
var self = this;
|
||||
flaggedPost.disagreeFlags().then(function () {
|
||||
self.removeObject(flaggedPost);
|
||||
}, function () {
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
},
|
||||
|
||||
deferFlags: function(item) {
|
||||
var adminFlagsController = this;
|
||||
item.deferFlags().then(function() {
|
||||
adminFlagsController.removeObject(item);
|
||||
}, function() {
|
||||
deferFlags: function (flaggedPost) {
|
||||
var self = this;
|
||||
flaggedPost.deferFlags().then(function () {
|
||||
self.removeObject(flaggedPost);
|
||||
}, function () {
|
||||
bootbox.alert(I18n.t("admin.flags.error"));
|
||||
});
|
||||
},
|
||||
|
@ -45,47 +43,8 @@ export default Ember.ArrayController.extend({
|
|||
doneTopicFlags: function(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(){
|
||||
var flags = this.get('model');
|
||||
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({
|
||||
|
||||
summary: function(){
|
||||
summary: function () {
|
||||
return _(this.post_actions)
|
||||
.groupBy(function(a){ return a.post_action_type_id; })
|
||||
.map(function(v,k){
|
||||
return I18n.t('admin.flags.summary.action_type_' + k, {count: v.length});
|
||||
})
|
||||
.groupBy(function (a) { return a.post_action_type_id; })
|
||||
.map(function (v,k) { return I18n.t('admin.flags.summary.action_type_' + k, { count: v.length }); })
|
||||
.join(',');
|
||||
}.property(),
|
||||
|
||||
flaggers: function() {
|
||||
var r,
|
||||
_this = this;
|
||||
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];
|
||||
}
|
||||
flaggers: function () {
|
||||
var self = this;
|
||||
var flaggers = [];
|
||||
|
||||
var flagType = I18n.t('admin.flags.summary.action_type_' + action.post_action_type_id, {count: 1});
|
||||
|
||||
r.push({
|
||||
user: user, flagType: flagType, flaggedAt: action.created_at, deletedBy: deletedBy,
|
||||
tookAction: action.staff_took_action, deletedAt: action.deleted_at
|
||||
_.each(this.post_actions, function (postAction) {
|
||||
flaggers.push({
|
||||
user: self.userLookup[postAction.user_id],
|
||||
topic: self.topicLookup[postAction.topic_id],
|
||||
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(),
|
||||
|
||||
messages: function() {
|
||||
var r,
|
||||
_this = this;
|
||||
r = [];
|
||||
_.each(this.post_actions,function(action) {
|
||||
if (action.message) {
|
||||
r.push({
|
||||
user: _this.userLookup[action.user_id],
|
||||
message: action.message,
|
||||
permalink: action.permalink,
|
||||
bySystemUser: (action.user_id === -1 ? true : false)
|
||||
});
|
||||
conversations: function () {
|
||||
var self = this;
|
||||
var conversations = [];
|
||||
|
||||
_.each(this.post_actions, function (postAction) {
|
||||
if (postAction.conversation) {
|
||||
var conversation = {
|
||||
permalink: postAction.permalink,
|
||||
hasMore: postAction.conversation.has_more,
|
||||
response: {
|
||||
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 this.post_actions[0].created_at;
|
||||
return conversations;
|
||||
}.property(),
|
||||
|
||||
user: function() {
|
||||
return this.userLookup[this.user_id];
|
||||
}.property(),
|
||||
|
||||
topicHidden: function() {
|
||||
return !this.get('topic_visible');
|
||||
}.property('topic_hidden'),
|
||||
topic: function () {
|
||||
return this.topicLookup[this.topic_id];
|
||||
}.property(),
|
||||
|
||||
flaggedForSpam: function() {
|
||||
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'),
|
||||
|
||||
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'),
|
||||
|
||||
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 });
|
||||
},
|
||||
|
||||
deferFlags: function() {
|
||||
return Discourse.ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false });
|
||||
deferFlags: function (deletePost) {
|
||||
return Discourse.ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } });
|
||||
},
|
||||
|
||||
agreeFlags: function() {
|
||||
return Discourse.ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false });
|
||||
agreeFlags: function (deletePost) {
|
||||
return Discourse.ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } });
|
||||
},
|
||||
|
||||
postHidden: Em.computed.alias('hidden'),
|
||||
|
||||
extraClasses: function() {
|
||||
var classes = [];
|
||||
if (this.get('hidden')) {
|
||||
classes.push('hidden-post');
|
||||
}
|
||||
if (this.get('deleted')){
|
||||
classes.push('deleted');
|
||||
}
|
||||
if (this.get('hidden')) { classes.push('hidden-post'); }
|
||||
if (this.get('deleted')) { classes.push('deleted'); }
|
||||
return classes.join(' ');
|
||||
}.property(),
|
||||
|
||||
|
@ -121,26 +122,36 @@ Discourse.FlaggedPost = Discourse.Post.extend({
|
|||
});
|
||||
|
||||
Discourse.FlaggedPost.reopenClass({
|
||||
findAll: function(filter, offset) {
|
||||
|
||||
findAll: function (filter, offset) {
|
||||
offset = offset || 0;
|
||||
|
||||
var result = Em.A();
|
||||
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 = {};
|
||||
_.each(data.users,function(user) {
|
||||
_.each(data.users,function (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);
|
||||
f.userLookup = userLookup;
|
||||
f.topicLookup = topicLookup;
|
||||
result.pushObject(f);
|
||||
});
|
||||
|
||||
result.set('loading', false);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -18,7 +18,16 @@ Discourse.AdminFlagsRouteType = Discourse.Route.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 class="admin-container">
|
||||
{{#if model.loading}}
|
||||
{{#if loading}}
|
||||
<div class='admin-loading'>{{i18n loading}}</div>
|
||||
{{else}}
|
||||
{{#if model.length}}
|
||||
{{#if length}}
|
||||
<table class='admin-flags'>
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -19,131 +19,141 @@
|
|||
<th class='excerpt'></th>
|
||||
<th class='flaggers'>{{i18n admin.flags.flagged_by}}</th>
|
||||
<th class='flaggers'>{{#if adminOldFlagsView}}{{i18n admin.flags.resolved_by}}{{/if}}</th>
|
||||
<th class='last-flagged'></th>
|
||||
<th class='action'></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each flaggedPost in content}}
|
||||
<tr {{bind-attr class="flaggedPost.extraClasses"}}>
|
||||
<tr {{bind-attr class="flaggedPost.extraClasses"}}>
|
||||
|
||||
<td class='user'>
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#if flaggedPost.user}}
|
||||
{{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="small"}}{{/link-to}}
|
||||
<td class='user'>
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#if flaggedPost.user}}
|
||||
{{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="small"}}{{/link-to}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</td>
|
||||
|
||||
<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>
|
||||
<br>
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{{flaggedPost.excerpt}}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class='excerpt'>
|
||||
<h3>
|
||||
{{#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}}
|
||||
{{{flaggedPost.excerpt}}}
|
||||
{{/if}}
|
||||
</td>
|
||||
|
||||
<td class='flaggers'>
|
||||
<table>
|
||||
<tbody>
|
||||
{{#each flaggedPost.flaggers}}
|
||||
<tr>
|
||||
<td>
|
||||
{{#link-to 'adminUser' this.user}}{{avatar this.user imageSize="small"}} {{/link-to}}
|
||||
</td>
|
||||
<td>
|
||||
{{date this.flaggedAt}}
|
||||
</td>
|
||||
<td>
|
||||
{{this.flagType}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td class='flaggers'>
|
||||
<table>
|
||||
<tbody>
|
||||
{{#each flaggedPost.flaggers}}
|
||||
<tr>
|
||||
<td width="20%">
|
||||
{{#link-to 'adminUser' user}}
|
||||
{{avatar user imageSize="small"}}
|
||||
{{/link-to}}
|
||||
</td>
|
||||
<td width="30%">
|
||||
{{date flaggedAt}}
|
||||
</td>
|
||||
<td width="50%">
|
||||
{{flagType}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
<td class='flaggers result'>
|
||||
<table>
|
||||
<tbody>
|
||||
{{#each flaggedPost.flaggers}}
|
||||
<tr>
|
||||
{{#if deletedBy}}
|
||||
<td>
|
||||
{{#link-to 'adminUser' this.deletedBy}}{{avatar this.deletedBy imageSize="small"}} {{/link-to}}
|
||||
</td>
|
||||
<td>
|
||||
{{#if this.tookAction}}
|
||||
<i class='fa fa-gavel'></i>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
{{date this.deletedAt}}
|
||||
</td>
|
||||
{{/if}}
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<td class='flaggers result'>
|
||||
<table>
|
||||
<tbody>
|
||||
{{#each flaggedPost.flaggers}}
|
||||
<tr>
|
||||
<td width="20%">
|
||||
{{#link-to 'adminUser' disposedBy}}
|
||||
{{avatar disposedBy imageSize="small"}}
|
||||
{{/link-to}}
|
||||
</td>
|
||||
<td width="30%">
|
||||
{{date disposedAt}}
|
||||
</td>
|
||||
<td width="50%">
|
||||
{{disposition}}
|
||||
{{#if tookAction}}
|
||||
<i class='fa fa-gavel'></i>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
{{#if flaggedPost.topicFlagged}}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class='message'><div>{{{i18n admin.flags.topic_flagged}}}</div></td>
|
||||
<td></td>
|
||||
<tr class='message'>
|
||||
<td></td>
|
||||
<td colspan="3">
|
||||
<div>
|
||||
{{{i18n admin.flags.topic_flagged}}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
{{#each flaggedPost.messages}}
|
||||
<tr>
|
||||
{{#each flaggedPost.conversations}}
|
||||
<tr class='message'>
|
||||
<td></td>
|
||||
<td class='message'>
|
||||
<td colspan="3">
|
||||
<div>
|
||||
{{#unless bySystemUser}}
|
||||
{{#link-to 'adminUser' user}}{{avatar user imageSize="small"}}{{/link-to}}
|
||||
{{message}}
|
||||
<a href="{{unbound permalink}}"><button class='btn'><i class="fa fa-reply"></i> {{i18n admin.flags.view_message}}</button></a>
|
||||
{{else}}
|
||||
<b>{{i18n admin.flags.system}}</b>:
|
||||
{{message}}
|
||||
{{/unless}}
|
||||
{{#if response}}
|
||||
<p>
|
||||
{{#link-to 'adminUser' response.user}}{{avatar response.user imageSize="small"}}{{/link-to}} {{{response.excerpt}}}
|
||||
</p>
|
||||
{{#if reply}}
|
||||
<p>
|
||||
{{#link-to 'adminUser' reply.user}}{{avatar reply.user imageSize="small"}}{{/link-to}} {{{reply.excerpt}}}
|
||||
{{#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>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
<tr>
|
||||
<tr>
|
||||
<td colspan="4" class="action">
|
||||
{{#if adminActiveFlagsView}}
|
||||
{{#if flaggedPost.topicFlagged}}
|
||||
<a href='{{unbound flaggedPost.url}}' class="btn">{{i18n admin.flags.visit_topic}}</a>
|
||||
{{/if}}
|
||||
{{#if adminActiveFlagsView}}
|
||||
{{#if flaggedPost.topicFlagged}}
|
||||
<a href='{{unbound flaggedPost.url}}' class="btn">{{i18n admin.flags.visit_topic}}</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#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.defer_title}}' class='btn' {{action deferFlags flaggedPost}}><i class="fa fa-external-link"></i> {{i18n admin.flags.defer}}</button>
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#if flaggedPost.postHidden}}
|
||||
<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>
|
||||
{{else}}
|
||||
<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_flag_title}}' class='btn' {{action disagreeFlags flaggedPost}}><i class="fa fa-thumbs-o-down"></i> {{i18n admin.flags.disagree_flag}}</button>
|
||||
{{/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>
|
||||
<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>
|
||||
{{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.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.clear_topic_flags_title}}' class='btn' {{action doneTopicFlags flaggedPost}}>{{i18n admin.flags.clear_topic_flags}}</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if flaggedPost.canDeleteAsSpammer}}
|
||||
<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}}
|
||||
<button title='{{i18n admin.flags.clear_topic_flags_title}}' class='btn' {{action doneTopicFlags flaggedPost}}>{{i18n admin.flags.clear_topic_flags}}</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
|
||||
{{/each}}
|
||||
|
||||
|
|
|
@ -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, {
|
||||
loading: false,
|
||||
eyelineSelector: '.admin-flags tbody tr',
|
||||
loadMore: function() {
|
||||
var view = this;
|
||||
if(this.get("loading") || this.get("model.allLoaded")) { return; }
|
||||
this.set("loading", true);
|
||||
this.get("controller").loadMore().then(function(){
|
||||
view.set("loading", false);
|
||||
});
|
||||
|
||||
actions: {
|
||||
|
||||
loadMore: function() {
|
||||
var self = this;
|
||||
|
||||
if (this.get("loading") || this.get("model.allLoaded")) { return; }
|
||||
|
||||
this.set("loading", true);
|
||||
|
||||
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('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_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>");
|
||||
});
|
||||
|
@ -77,8 +77,8 @@ export default Em.Component.extend({
|
|||
var $target = $(e.target),
|
||||
actionTypeId;
|
||||
|
||||
if (actionTypeId = $target.data('clear-flags')) {
|
||||
this.actionTypeById(actionTypeId).clearFlags();
|
||||
if (actionTypeId = $target.data('defer-flags')) {
|
||||
this.actionTypeById(actionTypeId).deferFlags();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,9 +47,7 @@ export default Ember.Component.extend({
|
|||
// Allow a plugin to add a custom icon to a topic
|
||||
this.trigger('addCustomIcon', buffer);
|
||||
|
||||
var togglePin = function(){
|
||||
|
||||
};
|
||||
var togglePin = function () {};
|
||||
|
||||
renderIconIf('topic.closed', 'lock', 'locked');
|
||||
renderIconIf('topic.archived', 'lock', 'archived');
|
||||
|
|
|
@ -68,7 +68,7 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
|||
|
||||
if(action === 'notify_moderators' || action === 'notify_user') {
|
||||
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
|
||||
|
@ -108,9 +108,9 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
|||
});
|
||||
},
|
||||
|
||||
clearFlags: function() {
|
||||
deferFlags: function() {
|
||||
var actionSummary = this;
|
||||
return Discourse.ajax("/post_actions/clear_flags", {
|
||||
return Discourse.ajax("/post_actions/defer_flags", {
|
||||
type: "POST",
|
||||
data: {
|
||||
post_action_type_id: this.get('id'),
|
||||
|
|
|
@ -13,20 +13,20 @@
|
|||
{{#if topic.isPrivateMessage}}
|
||||
<span class="private-message-glyph">{{icon envelope}}</span>
|
||||
{{/if}}
|
||||
{{#if topic.category.parentCategory}}
|
||||
{{bound-category-link topic.category.parentCategory}}
|
||||
{{/if}}
|
||||
{{bound-category-link topic.category}}
|
||||
{{#if topic.details.loaded}}
|
||||
{{topic-status topic=topic}}
|
||||
<a class='topic-link' href='{{unbound topic.url}}' {{action jumpToTopPost}}>{{{topic.fancy_title}}}</a>
|
||||
{{else}}
|
||||
{{#if topic.errorLoading}}
|
||||
{{topic.errorTitle}}
|
||||
{{else}}
|
||||
{{i18n topic.loading}}
|
||||
{{#if topic.category.parentCategory}}
|
||||
{{bound-category-link topic.category.parentCategory}}
|
||||
{{/if}}
|
||||
{{bound-category-link topic.category}}
|
||||
{{#if topic.details.loaded}}
|
||||
{{topic-status topic=topic}}
|
||||
<a class='topic-link' href='{{unbound topic.url}}' {{action jumpToTopPost}}>{{{topic.fancy_title}}}</a>
|
||||
{{else}}
|
||||
{{#if topic.errorLoading}}
|
||||
{{topic.errorTitle}}
|
||||
{{else}}
|
||||
{{i18n topic.loading}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
{{#if editingTopic}}
|
||||
{{#if isPrivateMessage}}
|
||||
<span class="private-message-glyph"><i class='fa fa-envelope'></i></span>
|
||||
<span class="private-message-glyph">{{icon envelope}}</span>
|
||||
{{else}}
|
||||
{{category-chooser valueAttribute="id" value=newCategoryId source=category_id}}
|
||||
{{/if}}
|
||||
|
|
|
@ -494,25 +494,30 @@ section.details {
|
|||
|
||||
.admin-flags {
|
||||
|
||||
tr.hidden-post td.excerpt { opacity: 0.4; }
|
||||
tr.deleted td.excerpt { opacity: 0.8; background-color: scale-color($danger, $lightness: 30%); }
|
||||
td.message {
|
||||
padding: 4px 8px;
|
||||
background-color: scale-color($highlight, $lightness: 30%);
|
||||
}
|
||||
.hidden-post td.excerpt { opacity: 0.5; }
|
||||
.deleted td.excerpt { background-color: scale-color($danger, $lightness: 70%); }
|
||||
.message { background-color: scale-color($highlight, $lightness: 70%); }
|
||||
.message:hover { background-color: scale-color($highlight, $lightness: 30%); }
|
||||
td { vertical-align: top; }
|
||||
th { text-align: left; }
|
||||
.user { width: 40px; padding-top: 12px; }
|
||||
.user {
|
||||
width: 20px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.excerpt {
|
||||
max-width: 740px;
|
||||
width: 740px;
|
||||
max-width: 720px;
|
||||
width: 720px;
|
||||
padding: 8px;
|
||||
word-wrap: break-word;
|
||||
.fa,h3 { display: inline-block; }
|
||||
|
||||
.fa { display: inline-block; }
|
||||
h3 {
|
||||
max-height: 1.2em;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.flaggers {
|
||||
font-size: 11px;
|
||||
padding: 8px 0 0 5px;
|
||||
td {
|
||||
vertical-align: middle;
|
||||
padding: 3px;
|
||||
|
@ -523,6 +528,10 @@ section.details {
|
|||
text-align: right;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
td p {
|
||||
font-size: 13px;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dashboard */
|
||||
|
@ -1135,6 +1144,17 @@ button.ru {
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.delete-flag-modal {
|
||||
.modal-inner-container {
|
||||
width: 400px;
|
||||
}
|
||||
button {
|
||||
display: block;
|
||||
margin: 10px 0 10px 10px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all
|
||||
and (max-width : 850px) {
|
||||
.nav-stacked {
|
||||
|
@ -1202,7 +1222,6 @@ and (max-width : 500px) {
|
|||
|
||||
.customize .content-list, .customize .current-style {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,37 +1,50 @@
|
|||
require 'flag_query'
|
||||
|
||||
class Admin::FlagsController < Admin::AdminController
|
||||
|
||||
def index
|
||||
# we may get out of sync, fix it here
|
||||
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?
|
||||
render json: {users: [], posts: []}
|
||||
render json: { posts: [], topics: [], users: [] }
|
||||
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
|
||||
|
||||
def disagree
|
||||
p = Post.find(params[:id])
|
||||
PostAction.clear_flags!(p, current_user.id)
|
||||
p.reload
|
||||
p.unhide!
|
||||
def agree
|
||||
params.permit(:id, :delete_post)
|
||||
post = Post.find(params[:id])
|
||||
post_action_type = PostAction.post_action_type_for_post(post.id)
|
||||
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 agree
|
||||
p = Post.find(params[:id])
|
||||
post_action_type = PostAction.post_action_type_for_post(p.id)
|
||||
PostAction.defer_flags!(p, current_user.id)
|
||||
PostAction.hide_post!(p, post_action_type)
|
||||
def disagree
|
||||
params.permit(:id)
|
||||
post = Post.find(params[:id])
|
||||
PostAction.clear_flags!(post, current_user)
|
||||
post.reload
|
||||
post.unhide!
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def defer
|
||||
p = Post.find(params[:id])
|
||||
PostAction.defer_flags!(p, current_user.id)
|
||||
params.permit(:id, :delete_post)
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
@ -181,7 +181,7 @@ class Admin::UsersController < Admin::AdminController
|
|||
end
|
||||
|
||||
def destroy
|
||||
user = User.find_by(id: params[:id])
|
||||
user = User.find_by(id: params[:id].to_i)
|
||||
guardian.ensure_can_delete_user!(user)
|
||||
begin
|
||||
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[: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'
|
||||
|
||||
post_action = PostAction.act(current_user, @post, @post_action_type_id, args)
|
||||
|
@ -46,17 +46,17 @@ class PostActionsController < ApplicationController
|
|||
render nothing: true
|
||||
end
|
||||
|
||||
def clear_flags
|
||||
guardian.ensure_can_clear_flags!(@post)
|
||||
def defer_flags
|
||||
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
|
||||
|
||||
if @post.is_flagged?
|
||||
render json: {success: true, hidden: true}
|
||||
render json: { success: true, hidden: true }
|
||||
else
|
||||
@post.unhide!
|
||||
render json: {success: true, hidden: false}
|
||||
render json: { success: true, hidden: false }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,17 +16,35 @@ class PostAction < ActiveRecord::Base
|
|||
rate_limit :post_action_rate_limiter
|
||||
|
||||
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 :enforce_rules
|
||||
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
|
||||
posts_flagged_count = PostAction.joins(post: :topic)
|
||||
.where('defer = false or defer IS NULL')
|
||||
.where('post_actions.post_action_type_id' => PostActionType.notify_flag_type_ids,
|
||||
'posts.deleted_at' => nil,
|
||||
'topics.deleted_at' => nil)
|
||||
posts_flagged_count = PostAction.active
|
||||
.flags
|
||||
.joins(post: :topic)
|
||||
.where('posts.deleted_at' => nil)
|
||||
.where('topics.deleted_at' => nil)
|
||||
.count('DISTINCT posts.id')
|
||||
|
||||
$redis.set('posts_flagged_count', posts_flagged_count)
|
||||
|
@ -39,58 +57,93 @@ class PostAction < ActiveRecord::Base
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
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 = {}
|
||||
result.each do |r|
|
||||
user_actions[r.post_id] ||= {}
|
||||
user_actions[r.post_id][r.post_action_type_id] = r
|
||||
post_actions.each do |post_action|
|
||||
user_actions[post_action.post_id] ||= {}
|
||||
user_actions[post_action.post_id][post_action.post_action_type_id] = post_action
|
||||
end
|
||||
|
||||
user_actions
|
||||
end
|
||||
|
||||
def self.count_per_day_for_type(sinceDaysAgo = 30, post_action_type)
|
||||
unscoped.where(post_action_type_id: post_action_type).where('created_at > ?', sinceDaysAgo.days.ago).group('date(created_at)').order('date(created_at)').count
|
||||
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 > ?', since_days_ago.days.ago)
|
||||
.group('date(created_at)')
|
||||
.order('date(created_at)')
|
||||
.count
|
||||
end
|
||||
|
||||
def self.clear_flags!(post, moderator_id, action_type_id = nil)
|
||||
# -1 is the automatic system cleary
|
||||
actions = if action_type_id
|
||||
[action_type_id]
|
||||
else
|
||||
moderator_id == -1 ? PostActionType.auto_action_flag_types.values : PostActionType.flag_types.values
|
||||
end
|
||||
def self.agree_flags!(post, moderator, delete_post=false)
|
||||
actions = PostAction.active
|
||||
.where(post_id: post.id)
|
||||
.where(post_action_type_id: PostActionType.flag_types.values)
|
||||
|
||||
PostAction.where({ post_id: post.id, post_action_type_id: actions }).update_all({ deleted_at: Time.zone.now, deleted_by_id: moderator_id })
|
||||
f = actions.map{|t| ["#{PostActionType.types[t]}_count", 0]}
|
||||
Post.where(id: post.id).with_deleted.update_all(Hash[*f.flatten])
|
||||
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
|
||||
actions.each do |action|
|
||||
action.agreed_at = Time.zone.now
|
||||
action.agreed_by_id = moderator.id
|
||||
# so callback is called
|
||||
a.save
|
||||
action.save
|
||||
action.add_moderator_post_if_needed(moderator, :agreed, delete_post)
|
||||
end
|
||||
|
||||
update_flagged_posts_count
|
||||
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)
|
||||
post_action_type = PostActionType.types[post_action_type_id]
|
||||
|
||||
|
@ -123,10 +176,10 @@ class PostAction < ActiveRecord::Base
|
|||
end
|
||||
|
||||
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] and post.topic
|
||||
targets_topic = if opts[:flag_topic] && post.topic
|
||||
post.topic.reload
|
||||
post.topic.posts_count != 1
|
||||
end
|
||||
|
@ -138,17 +191,16 @@ class PostAction < ActiveRecord::Base
|
|||
}
|
||||
|
||||
action_attributes = {
|
||||
message: opts[:message],
|
||||
staff_took_action: opts[:take_action] || false,
|
||||
staff_took_action: staff_took_action,
|
||||
related_post_id: related_post_id,
|
||||
targets_topic: !!targets_topic
|
||||
}
|
||||
|
||||
# First try to revive a trashed record
|
||||
row_count = PostAction.where(where_attrs)
|
||||
.with_deleted
|
||||
.where("deleted_at IS NOT NULL")
|
||||
.update_all(action_attributes.merge(deleted_at: nil))
|
||||
.with_deleted
|
||||
.where("deleted_at IS NOT NULL")
|
||||
.update_all(action_attributes.merge(deleted_at: nil))
|
||||
|
||||
if row_count == 0
|
||||
post_action = create(where_attrs.merge(action_attributes))
|
||||
|
@ -157,9 +209,13 @@ class PostAction < ActiveRecord::Base
|
|||
end
|
||||
else
|
||||
post_action = PostAction.where(where_attrs).first
|
||||
post_action.update_counters
|
||||
end
|
||||
|
||||
# agree with other flags
|
||||
PostAction.agree_flags!(post, user) if staff_took_action
|
||||
# update counters
|
||||
post_action.try(:update_counters)
|
||||
|
||||
post_action
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
# can happen despite being .create
|
||||
|
@ -216,10 +272,11 @@ class PostAction < ActiveRecord::Base
|
|||
|
||||
before_create do
|
||||
post_action_type_ids = is_flag? ? PostActionType.flag_types.values : post_action_type_id
|
||||
raise AlreadyActed if PostAction.where(user_id: user_id,
|
||||
post_id: post_id,
|
||||
post_action_type_id: post_action_type_ids,
|
||||
deleted_at: nil)
|
||||
raise AlreadyActed if PostAction.where(user_id: user_id)
|
||||
.where(post_id: post_id)
|
||||
.where(post_action_type_id: post_action_type_ids)
|
||||
.where(deleted_at: nil)
|
||||
.where(targets_topic: targets_topic)
|
||||
.exists?
|
||||
end
|
||||
|
||||
|
@ -251,30 +308,30 @@ class PostAction < ActiveRecord::Base
|
|||
PostActionType.types[post_action_type_id]
|
||||
end
|
||||
|
||||
|
||||
def update_counters
|
||||
# Update denormalized counts
|
||||
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.
|
||||
case post_action_type_key
|
||||
when :vote
|
||||
# Voting also changes the sort_order
|
||||
Post.where(id: post_id).update_all ["vote_count = vote_count + :delta, sort_order = :max - (vote_count + :delta)",
|
||||
delta: delta,
|
||||
max: Topic.max_sort_order]
|
||||
Post.where(id: post_id).update_all ["vote_count = :count, sort_order = :max - :count", count: count, max: Topic.max_sort_order]
|
||||
when :like
|
||||
# `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",
|
||||
delta: delta,
|
||||
score_delta: user.staff? ? delta * SiteSetting.staff_like_weight : delta]
|
||||
score = PostAction.joins(:user)
|
||||
.where(post_id: post_id)
|
||||
.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
|
||||
Post.where(id: post_id).update_all ["#{column} = #{column} + ?", delta]
|
||||
Post.where(id: post_id).update_all ["#{column} = ?", count]
|
||||
end
|
||||
|
||||
post = Post.with_deleted.where(id: post_id).first
|
||||
Topic.where(id: post.topic_id).update_all ["#{column} = #{column} + ?", delta]
|
||||
topic_id = Post.with_deleted.where(id: post_id).pluck(:topic_id).first
|
||||
Topic.where(id: topic_id).update_all ["#{column} = ?", count]
|
||||
|
||||
if PostActionType.notify_flag_type_ids.include?(post_action_type_id)
|
||||
PostAction.update_flagged_posts_count
|
||||
|
@ -314,7 +371,6 @@ class PostAction < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def self.hide_post!(post, post_action_type, reason=nil)
|
||||
return if post.hidden
|
||||
|
||||
|
@ -324,8 +380,7 @@ class PostAction < ActiveRecord::Base
|
|||
end
|
||||
|
||||
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_id: post.topic_id]).update_all(visible: false)
|
||||
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)
|
||||
|
||||
# inform user
|
||||
if post.user
|
||||
|
@ -345,7 +400,7 @@ class PostAction < ActiveRecord::Base
|
|||
end
|
||||
|
||||
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]
|
||||
end
|
||||
|
||||
|
@ -366,15 +421,17 @@ end
|
|||
# user_id :integer not null
|
||||
# post_action_type_id :integer not null
|
||||
# deleted_at :datetime
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# deleted_by_id :integer
|
||||
# message :text
|
||||
# related_post_id :integer
|
||||
# staff_took_action :boolean default(FALSE), not null
|
||||
# defer :boolean
|
||||
# defer_by :integer
|
||||
# defered_at :datetime
|
||||
# defer_by_id :integer
|
||||
# targets_topic :boolean default(FALSE)
|
||||
# agreed_at :datetime
|
||||
# agreed_by_id :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -19,6 +19,10 @@ class PostActionType < ActiveRecord::Base
|
|||
@public_types ||= types.except(*flag_types.keys << :notify_user)
|
||||
end
|
||||
|
||||
def public_type_ids
|
||||
@public_type_ids ||= public_types.values
|
||||
end
|
||||
|
||||
def flag_types
|
||||
@flag_types ||= types.only(:off_topic, :spam, :inappropriate, :notify_moderators)
|
||||
end
|
||||
|
|
|
@ -115,8 +115,8 @@ class Report
|
|||
|
||||
def self.post_action_report(report, post_action_type)
|
||||
report.data = []
|
||||
PostAction.count_per_day_for_type(30, post_action_type).each do |date, count|
|
||||
report.data << {x: date, y: count}
|
||||
PostAction.count_per_day_for_type(post_action_type).each do |date, count|
|
||||
report.data << { x: date, y: count }
|
||||
end
|
||||
query = PostAction.unscoped.where(post_action_type_id: post_action_type)
|
||||
report.total = query.count
|
||||
|
|
|
@ -477,7 +477,6 @@ class Topic < ActiveRecord::Base
|
|||
topic_id: self.id)
|
||||
new_post = creator.create
|
||||
increment!(:moderator_posts_count)
|
||||
new_post
|
||||
end
|
||||
|
||||
if new_post.present?
|
||||
|
|
|
@ -182,9 +182,12 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def created_topic_count
|
||||
topics.count
|
||||
stat = user_stat || create_user_stat
|
||||
stat.topic_count
|
||||
end
|
||||
|
||||
alias_method :topic_count, :created_topic_count
|
||||
|
||||
# tricky, we need our bus to be subscribed from the right spot
|
||||
def sync_notification_channel_position
|
||||
@unread_notifications_by_type = nil
|
||||
|
@ -370,11 +373,8 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def post_count
|
||||
posts.count
|
||||
end
|
||||
|
||||
def first_post
|
||||
posts.order('created_at ASC').first
|
||||
stat = user_stat || create_user_stat
|
||||
stat.post_count
|
||||
end
|
||||
|
||||
def flags_given_count
|
||||
|
@ -607,6 +607,10 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def first_post_created_at
|
||||
user_stat.try(:first_post_created_at)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def badge_grant
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
class FlaggedTopicSerializer < ActiveModel::Serializer
|
||||
attributes :id,
|
||||
:title,
|
||||
:slug,
|
||||
:archived,
|
||||
:closed,
|
||||
:visible,
|
||||
:archetype,
|
||||
:relative_url
|
||||
end
|
|
@ -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
|
||||
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
|
||||
|
||||
if post_actions.present? && post_actions.has_key?(id)
|
||||
|
|
|
@ -173,7 +173,7 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
count: 0,
|
||||
hidden: false,
|
||||
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
|
||||
result
|
||||
end
|
||||
|
|
|
@ -1044,9 +1044,9 @@ en:
|
|||
|
||||
actions:
|
||||
flag: 'Flag'
|
||||
clear_flags:
|
||||
one: "Clear flag"
|
||||
other: "Clear flags"
|
||||
defer_flags:
|
||||
one: "Defer flag"
|
||||
other: "Defer flags"
|
||||
it_too:
|
||||
off_topic: "Flag it too"
|
||||
spam: "Flag it too"
|
||||
|
@ -1411,25 +1411,37 @@ en:
|
|||
old: "Old"
|
||||
active: "Active"
|
||||
|
||||
agree_hide: "Agree (hide post + send PM)"
|
||||
agree_hide_title: "Hide this post and automatically send the user a private message urging them to edit it"
|
||||
defer: "Defer"
|
||||
defer_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_post_title: "Delete post; if the first post, delete the topic"
|
||||
disagree_unhide: "Disagree (unhide post)"
|
||||
disagree_unhide_title: "Remove any flags from this post and make the post visible again"
|
||||
disagree: "Disagree"
|
||||
disagree_title: "Disagree with flag, remove any flags from this post"
|
||||
agree_flag_hide_post: "Agree (hide post + send PM)"
|
||||
agree_flag_hide_post_title: "Hide this post and automatically send the user a private message urging them to edit it"
|
||||
defer_flag: "Defer"
|
||||
defer_flag_title: "No action is necessary at this time, defer any action on this flag until a later date, or never"
|
||||
delete: "Delete"
|
||||
delete_title: "Delete"
|
||||
delete_post_defer_flag: "Delete Post and defer flag"
|
||||
delete_post_defer_flag_title: "Delete post; if the first post, delete the topic"
|
||||
delete_post_agree_flag: "Delete Post and agree with flag"
|
||||
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."
|
||||
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_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"
|
||||
resolved_by: "Resolved by"
|
||||
system: "System"
|
||||
error: "Something went wrong"
|
||||
view_message: "Reply"
|
||||
reply_message: "Reply"
|
||||
no_results: "There are no flags."
|
||||
topic_flagged: "This <strong>topic</strong> has been flagged."
|
||||
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."
|
||||
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:
|
||||
post_hidden:
|
||||
subject_template: "Post hidden due to community flagging"
|
||||
|
|
|
@ -273,7 +273,7 @@ Discourse::Application.routes.draw do
|
|||
resources :post_actions do
|
||||
collection do
|
||||
get "users"
|
||||
post "clear_flags"
|
||||
post "defer_flags"
|
||||
end
|
||||
end
|
||||
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
|
|
@ -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
|
|
@ -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
|
||||
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)
|
||||
|
||||
guardian = Guardian.new(current_user)
|
||||
|
||||
if !guardian.is_admin?
|
||||
actions = actions.joins(:post => :topic)
|
||||
.where('category_id in (?)', guardian.allowed_category_ids)
|
||||
actions = actions.where('category_id in (?)', guardian.allowed_category_ids)
|
||||
end
|
||||
|
||||
post_ids = actions
|
||||
.limit(per_page)
|
||||
.offset(offset)
|
||||
.group(:post_id)
|
||||
.order('min(post_actions.created_at) DESC')
|
||||
.pluck(:post_id).uniq
|
||||
post_ids = actions.limit(per_page)
|
||||
.offset(offset)
|
||||
.group(:post_id)
|
||||
.order('min(post_actions.created_at) DESC')
|
||||
.pluck(:post_id)
|
||||
.uniq
|
||||
|
||||
return nil if post_ids.blank?
|
||||
|
||||
actions = actions
|
||||
.order('post_actions.created_at DESC')
|
||||
.includes({:related_post => :topic})
|
||||
|
||||
posts = SqlBuilder.new("SELECT p.id, t.title, p.cooked, p.user_id,
|
||||
p.topic_id, p.post_number, p.hidden, t.visible topic_visible,
|
||||
p.deleted_at, t.deleted_at topic_deleted_at
|
||||
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)
|
||||
posts = SqlBuilder.new("
|
||||
SELECT p.id,
|
||||
p.cooked,
|
||||
p.user_id,
|
||||
p.topic_id,
|
||||
p.post_number,
|
||||
p.hidden,
|
||||
p.deleted_at
|
||||
FROM posts p
|
||||
WHERE p.id in (:post_ids)").map_exec(OpenStruct, post_ids: post_ids)
|
||||
|
||||
post_lookup = {}
|
||||
users = Set.new
|
||||
user_ids = Set.new
|
||||
topic_ids = Set.new
|
||||
|
||||
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.topic_slug = Slug.for(p.title)
|
||||
p.delete_field(:cooked)
|
||||
post_lookup[p.id] = p
|
||||
end
|
||||
|
||||
# maintain order
|
||||
posts = post_ids.map{|id| post_lookup[id]}
|
||||
|
||||
post_actions = actions.where(:post_id => post_ids)
|
||||
post_actions = actions.order('post_actions.created_at DESC')
|
||||
.includes(related_post: { topic: { posts: :user }})
|
||||
.where(post_id: post_ids)
|
||||
|
||||
post_actions.each do |pa|
|
||||
post = post_lookup[pa.post_id]
|
||||
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)
|
||||
if (pa.related_post && pa.related_post.topic)
|
||||
action.merge!(topic_id: pa.related_post.topic_id,
|
||||
slug: pa.related_post.topic.slug,
|
||||
permalink: pa.related_post.topic.url)
|
||||
|
||||
if pa.related_post && pa.related_post.topic
|
||||
conversation = {}
|
||||
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
|
||||
|
||||
action.merge!(permalink: related_topic.relative_url, conversation: conversation)
|
||||
end
|
||||
|
||||
post.post_actions << action
|
||||
users << pa.user_id
|
||||
users << pa.deleted_by_id if pa.deleted_by_id
|
||||
|
||||
user_ids << pa.user_id
|
||||
user_ids << pa.disposed_by_id if pa.disposed_by_id
|
||||
end
|
||||
|
||||
# TODO add serializer so we can skip this
|
||||
# maintain order
|
||||
posts = post_ids.map { |id| post_lookup[id] }
|
||||
# TODO: add serializer so we can skip this
|
||||
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
|
||||
|
||||
protected
|
||||
|
||||
def self.flagged_post_ids(filter, offset, limit)
|
||||
<<SQL
|
||||
def self.flagged_post_actions(filter)
|
||||
post_actions = PostAction.flags
|
||||
.joins("INNER JOIN posts ON posts.id = post_actions.post_id")
|
||||
.joins("INNER JOIN topics ON topics.id = posts.topic_id")
|
||||
|
||||
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*/
|
||||
if filter == "old"
|
||||
post_actions.with_deleted
|
||||
.where("post_actions.deleted_at IS NOT NULL OR
|
||||
post_actions.defered_at IS NOT NULL OR
|
||||
post_actions.agreed_at IS NOT NULL")
|
||||
else
|
||||
post_actions.active
|
||||
.where("posts.deleted_at" => nil)
|
||||
.where("topics.deleted_at" => nil)
|
||||
end
|
||||
|
||||
SQL
|
||||
end
|
||||
|
||||
def self.flagged_post_actions(filter)
|
||||
post_actions = PostAction
|
||||
.where(post_action_type_id: PostActionType.notify_flag_type_ids)
|
||||
.joins(:post => :topic)
|
||||
|
||||
if filter == 'old'
|
||||
post_actions
|
||||
.with_deleted
|
||||
.where('post_actions.deleted_at IS NOT NULL OR
|
||||
defer = true OR
|
||||
topics.deleted_at IS NOT NULL OR
|
||||
posts.deleted_at IS NOT NULL')
|
||||
else
|
||||
post_actions
|
||||
.where('defer IS NULL OR
|
||||
defer = false')
|
||||
.where('posts.deleted_at IS NULL AND
|
||||
topics.deleted_at IS NULL')
|
||||
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
|
||||
|
|
|
@ -29,7 +29,7 @@ module PostGuardian
|
|||
end
|
||||
end
|
||||
|
||||
def can_clear_flags?(post)
|
||||
def can_defer_flags?(post)
|
||||
is_staff? && post
|
||||
end
|
||||
|
||||
|
@ -54,7 +54,11 @@ module PostGuardian
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
# Creating Method
|
||||
|
|
|
@ -35,12 +35,11 @@ module UserGuardian
|
|||
end
|
||||
|
||||
def can_delete_user?(user)
|
||||
return false if user.nil?
|
||||
return false if user.admin?
|
||||
return false if user.nil? || user.admin?
|
||||
if is_me?(user)
|
||||
user.post_count <= 1
|
||||
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
|
||||
|
||||
|
|
|
@ -61,7 +61,6 @@ class PostCreator
|
|||
save_post
|
||||
extract_links
|
||||
store_unique_post_key
|
||||
consider_clearing_flags
|
||||
track_topic
|
||||
update_topic_stats
|
||||
update_user_counts
|
||||
|
@ -147,21 +146,6 @@ class PostCreator
|
|||
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
|
||||
|
||||
def setup_topic
|
||||
|
@ -233,20 +217,23 @@ class PostCreator
|
|||
@post.store_unique_post_key
|
||||
end
|
||||
|
||||
def consider_clearing_flags
|
||||
return if @opts[:import_mode]
|
||||
return unless @topic.private_message? && @post.post_number > 1 && @topic.user_id != @post.user_id
|
||||
|
||||
clear_possible_flags(@topic)
|
||||
end
|
||||
|
||||
def update_user_counts
|
||||
@user.create_user_stat if @user.user_stat.nil?
|
||||
|
||||
if @user.user_stat.first_post_created_at.nil?
|
||||
@user.user_stat.first_post_created_at = @post.created_at
|
||||
end
|
||||
|
||||
@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
|
||||
if !@opts[:import_mode] && @user.id != @topic.user_id
|
||||
@user.user_stat.update_topic_reply_count
|
||||
@user.user_stat.save!
|
||||
end
|
||||
|
||||
@user.user_stat.save!
|
||||
|
||||
@user.last_posted_at = @post.created_at
|
||||
@user.save!
|
||||
end
|
||||
|
|
|
@ -62,7 +62,8 @@ class PostDestroyer
|
|||
feature_users_in_the_topic
|
||||
Topic.reset_highest(@post.topic_id)
|
||||
end
|
||||
trash_post_actions
|
||||
trash_public_post_actions
|
||||
agree_with_flags
|
||||
trash_user_actions
|
||||
@post.update_flagged_posts_count
|
||||
remove_associated_replies
|
||||
|
@ -130,15 +131,18 @@ class PostDestroyer
|
|||
Jobs.enqueue(:feature_topic_users, topic_id: @post.topic_id, except_post_id: @post.id)
|
||||
end
|
||||
|
||||
def trash_post_actions
|
||||
@post.post_actions.each do |pa|
|
||||
pa.trash!(@user)
|
||||
end
|
||||
def trash_public_post_actions
|
||||
public_post_actions = PostAction.publics.where(post_id: @post.id)
|
||||
public_post_actions.each { |pa| pa.trash!(@user) }
|
||||
|
||||
f = PostActionType.types.map{|k,v| ["#{k}_count", 0]}
|
||||
f = PostActionType.public_types.map { |k,v| ["#{k}_count", 0] }
|
||||
Post.with_deleted.where(id: @post.id).update_all(Hash[*f.flatten])
|
||||
end
|
||||
|
||||
def agree_with_flags
|
||||
PostAction.agree_flags!(@post, @user, delete_post: true)
|
||||
end
|
||||
|
||||
def trash_user_actions
|
||||
UserAction.where(target_post_id: @post.id).each do |ua|
|
||||
row = {
|
||||
|
|
|
@ -29,9 +29,7 @@ class PostJobsEnqueuer
|
|||
end
|
||||
|
||||
def after_post_create
|
||||
if @post.post_number > 1
|
||||
TopicTrackingState.publish_unread(@post)
|
||||
end
|
||||
TopicTrackingState.publish_unread(@post) if @post.post_number > 1
|
||||
|
||||
Jobs.enqueue_in(
|
||||
SiteSetting.email_time_window_mins.minutes,
|
||||
|
|
|
@ -105,7 +105,7 @@ class PostRevisor
|
|||
@post.hidden_at = nil
|
||||
@post.topic.update_attributes(visible: true)
|
||||
|
||||
PostAction.clear_flags!(@post, -1)
|
||||
PostAction.clear_flags!(@post, Discourse.system_user)
|
||||
end
|
||||
|
||||
@post.extract_quoted_post_numbers
|
||||
|
|
|
@ -23,17 +23,19 @@ describe FlagQuery do
|
|||
PostAction.act(codinghorror, 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
|
||||
first = posts.first
|
||||
|
||||
users.count.should == 5
|
||||
first[:post_actions].count.should == 2
|
||||
|
||||
topics.count.should == 2
|
||||
|
||||
second = posts[1]
|
||||
|
||||
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.count.should == 1
|
||||
|
|
|
@ -81,25 +81,25 @@ describe Guardian do
|
|||
end
|
||||
|
||||
|
||||
describe "can_clear_flags" do
|
||||
describe "can_defer_flags" do
|
||||
let(:post) { Fabricate(:post) }
|
||||
let(:user) { post.user }
|
||||
let(:moderator) { Fabricate(:moderator) }
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
@ -1350,7 +1350,7 @@ describe Guardian do
|
|||
end
|
||||
|
||||
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) }
|
||||
|
||||
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
|
||||
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)
|
||||
Guardian.new(actor).can_delete_user?(user).should == true
|
||||
end
|
||||
|
@ -1386,7 +1386,7 @@ describe Guardian do
|
|||
|
||||
it "is false if user's first post is too old" do
|
||||
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)
|
||||
Guardian.new(actor).can_delete_user?(user).should == false
|
||||
end
|
||||
|
@ -1419,19 +1419,19 @@ describe Guardian do
|
|||
shared_examples "can_delete_all_posts examples" do
|
||||
it "is true if user has no posts" do
|
||||
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
|
||||
|
||||
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.stubs(:first_post).returns(Fabricate.build(:post, created_at: 9.days.ago))
|
||||
user = Fabricate(:user, created_at: 100.days.ago)
|
||||
user.stubs(:first_post_created_at).returns(9.days.ago)
|
||||
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
||||
Guardian.new(actor).can_delete_all_posts?(user).should be_true
|
||||
end
|
||||
|
||||
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.stubs(:first_post).returns(Fabricate.build(:post, created_at: 11.days.ago))
|
||||
user = Fabricate(:user, created_at: 100.days.ago)
|
||||
user.stubs(:first_post_created_at).returns(11.days.ago)
|
||||
SiteSetting.stubs(:delete_user_max_post_age).returns(10)
|
||||
Guardian.new(actor).can_delete_all_posts?(user).should be_false
|
||||
end
|
||||
|
@ -1441,14 +1441,14 @@ describe Guardian do
|
|||
end
|
||||
|
||||
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)
|
||||
SiteSetting.stubs(:delete_all_posts_max).returns(10)
|
||||
Guardian.new(actor).can_delete_all_posts?(u).should be_true
|
||||
end
|
||||
|
||||
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)
|
||||
SiteSetting.stubs(:delete_all_posts_max).returns(10)
|
||||
Guardian.new(actor).can_delete_all_posts?(u).should be_false
|
||||
|
@ -1528,7 +1528,7 @@ describe Guardian do
|
|||
end
|
||||
|
||||
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"
|
||||
|
||||
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)
|
||||
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
|
||||
include_examples "staff can always change usernames"
|
||||
|
|
|
@ -263,28 +263,24 @@ describe PostDestroyer do
|
|||
end
|
||||
|
||||
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!(: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
|
||||
PostAction.act(codinghorror, second_post, PostActionType.types[:off_topic])
|
||||
expect { PostDestroyer.new(moderator, second_post).destroy }.to change(PostAction, :flagged_posts_count).by(-1)
|
||||
end
|
||||
it "should delete public post actions and agree with flags" do
|
||||
second_post.expects(:update_flagged_posts_count)
|
||||
|
||||
it "should delete the post actions" do
|
||||
flag = PostAction.act(codinghorror, second_post, PostActionType.types[:off_topic])
|
||||
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.act(codinghorror, second_post, PostActionType.types[:off_topic])
|
||||
PostDestroyer.new(moderator, second_post.reload).destroy
|
||||
PostAction.find_by(id: bookmark.id).should == nil
|
||||
|
||||
off_topic = PostAction.find_by(id: flag.id)
|
||||
off_topic.should_not == nil
|
||||
off_topic.agreed_at.should_not == nil
|
||||
|
||||
second_post.reload
|
||||
expect(second_post.off_topic_count).to eq(0)
|
||||
expect(second_post.bookmark_count).to eq(0)
|
||||
second_post.bookmark_count.should == 0
|
||||
second_post.off_topic_count.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -307,17 +307,26 @@ describe Admin::UsersController do
|
|||
response.should be_forbidden
|
||||
end
|
||||
|
||||
it "returns an error if the user has posts" do
|
||||
Fabricate(:post, user: @delete_me)
|
||||
xhr :delete, :destroy, id: @delete_me.id
|
||||
response.should be_forbidden
|
||||
end
|
||||
context "user has post" do
|
||||
|
||||
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
|
||||
response.should be_forbidden
|
||||
end
|
||||
|
||||
it "doesn't return an error if delete_posts == true" do
|
||||
UserDestroyer.any_instance.expects(:destroy).with(@user, has_entry('delete_posts' => true)).returns(true)
|
||||
xhr :delete, :destroy, id: @delete_me.id, delete_posts: true
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
it "doesn't return an error if the user has posts and delete_posts == true" do
|
||||
Fabricate(:post, user: @delete_me)
|
||||
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
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
it "deletes the user record" do
|
||||
|
|
|
@ -102,13 +102,13 @@ describe PostActionsController do
|
|||
|
||||
end
|
||||
|
||||
context 'clear_flags' do
|
||||
context 'defer_flags' do
|
||||
|
||||
let(:flagged_post) { Fabricate(:post, user: Fabricate(:coding_horror)) }
|
||||
|
||||
context "not logged in" 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
|
||||
|
||||
|
@ -116,43 +116,38 @@ describe PostActionsController do
|
|||
let!(:user) { log_in(:moderator) }
|
||||
|
||||
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
|
||||
|
||||
it "raises an error when the user doesn't have access" do
|
||||
Guardian.any_instance.expects(:can_clear_flags?).returns(false)
|
||||
xhr :post, :clear_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
||||
Guardian.any_instance.expects(:can_defer_flags?).returns(false)
|
||||
xhr :post, :defer_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
||||
response.should be_forbidden
|
||||
end
|
||||
|
||||
context "success" do
|
||||
before do
|
||||
Guardian.any_instance.expects(:can_clear_flags?).returns(true)
|
||||
PostAction.expects(:clear_flags!).with(flagged_post, user.id, PostActionType.types[:spam])
|
||||
Guardian.any_instance.expects(:can_defer_flags?).returns(true)
|
||||
PostAction.expects(:defer_flags!).with(flagged_post, user)
|
||||
end
|
||||
|
||||
it "delegates to clear_flags" do
|
||||
xhr :post, :clear_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
||||
it "delegates to defer_flags" do
|
||||
xhr :post, :defer_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
it "works with a deleted post" do
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
describe 'users' do
|
||||
|
||||
let!(:post) { Fabricate(:post, user: log_in) }
|
||||
|
@ -188,6 +183,4 @@ describe PostActionsController do
|
|||
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
|
|
@ -12,7 +12,6 @@ describe PostAction do
|
|||
let(:post) { Fabricate(:post) }
|
||||
let(:bookmark) { PostAction.new(user_id: post.user_id, post_action_type_id: PostActionType.types[:bookmark] , post_id: post.id) }
|
||||
|
||||
|
||||
describe "messaging" do
|
||||
|
||||
it "notify moderators integration test" do
|
||||
|
@ -41,13 +40,12 @@ describe PostAction do
|
|||
# Notification level should be "Watching" for everyone
|
||||
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.create
|
||||
|
||||
action.reload
|
||||
action.deleted_at.should_not be_nil
|
||||
|
||||
action.deleted_at.should be_nil
|
||||
end
|
||||
|
||||
describe 'notify_moderators' do
|
||||
|
@ -87,7 +85,7 @@ describe PostAction do
|
|||
PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
|
||||
PostAction.flagged_posts_count.should == 1
|
||||
|
||||
PostAction.clear_flags!(post, -1)
|
||||
PostAction.clear_flags!(post, Discourse.system_user)
|
||||
PostAction.flagged_posts_count.should == 0
|
||||
end
|
||||
|
||||
|
@ -103,7 +101,7 @@ describe PostAction do
|
|||
PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
|
||||
post.hidden.should be_false
|
||||
post.hidden_at.should be_blank
|
||||
PostAction.defer_flags!(post, admin.id)
|
||||
PostAction.defer_flags!(post, admin)
|
||||
PostAction.flagged_posts_count.should == 0
|
||||
post.reload
|
||||
post.hidden.should be_false
|
||||
|
@ -220,7 +218,7 @@ describe PostAction do
|
|||
|
||||
# If staff takes action, it is ranked higher
|
||||
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]
|
||||
|
||||
# If a flag is dismissed
|
||||
|
@ -252,7 +250,7 @@ describe PostAction do
|
|||
post.reload
|
||||
post.spam_count.should == 1
|
||||
|
||||
PostAction.clear_flags!(post, -1)
|
||||
PostAction.clear_flags!(post, Discourse.system_user)
|
||||
post.reload
|
||||
|
||||
post.spam_count.should == 0
|
||||
|
|
|
@ -764,10 +764,6 @@ describe Topic do
|
|||
topic.moderator_posts_count.should == 0
|
||||
end
|
||||
|
||||
it "its user has a topics_count of 1" do
|
||||
topic.user.created_topic_count.should == 1
|
||||
end
|
||||
|
||||
context 'post' do
|
||||
let(:post) { Fabricate(:post, topic: topic, user: topic.user) }
|
||||
|
||||
|
|
|
@ -81,6 +81,10 @@ describe UserDestroyer do
|
|||
|
||||
context "delete_posts is false" do
|
||||
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
|
||||
expect { destroy rescue nil }.to_not change { User.count }
|
||||
|
|
Loading…
Reference in New Issue