diff --git a/app/assets/javascripts/admin/controllers/admin-flags.js.es6 b/app/assets/javascripts/admin/controllers/admin-flags.js.es6
index 7e4542cd8d5..93cc6d6772d 100644
--- a/app/assets/javascripts/admin/controllers/admin-flags.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-flags.js.es6
@@ -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){
diff --git a/app/assets/javascripts/admin/controllers/admin_delete_flag_controller.js b/app/assets/javascripts/admin/controllers/admin_delete_flag_controller.js
new file mode 100644
index 00000000000..89ee5371dd5
--- /dev/null
+++ b/app/assets/javascripts/admin/controllers/admin_delete_flag_controller.js
@@ -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(); });
+ }
+ }
+
+});
diff --git a/app/assets/javascripts/admin/models/flagged_post.js b/app/assets/javascripts/admin/models/flagged_post.js
index 8e424baf1c4..98110d0e2c9 100644
--- a/app/assets/javascripts/admin/models/flagged_post.js
+++ b/app/assets/javascripts/admin/models/flagged_post.js
@@ -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;
});
}
});
-
-
diff --git a/app/assets/javascripts/admin/routes/admin_flags_route.js b/app/assets/javascripts/admin/routes/admin_flags_route.js
index 8941d7010f9..a2bf110ea60 100644
--- a/app/assets/javascripts/admin/routes/admin_flags_route.js
+++ b/app/assets/javascripts/admin/routes/admin_flags_route.js
@@ -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');
+ }
+
+ }
});
diff --git a/app/assets/javascripts/admin/templates/flags.js.handlebars b/app/assets/javascripts/admin/templates/flags.js.handlebars
index 5329ec63ebc..52224ed9010 100644
--- a/app/assets/javascripts/admin/templates/flags.js.handlebars
+++ b/app/assets/javascripts/admin/templates/flags.js.handlebars
@@ -8,10 +8,10 @@
- {{#if model.loading}}
+ {{#if loading}}
{{i18n loading}}
{{else}}
- {{#if model.length}}
+ {{#if length}}
@@ -19,131 +19,141 @@
|
{{i18n admin.flags.flagged_by}} |
{{#if adminOldFlagsView}}{{i18n admin.flags.resolved_by}}{{/if}} |
- |
- |
{{#each flaggedPost in content}}
-
+
+
+
+ {{#each flaggedPost.flaggers}}
+
+
+ {{#link-to 'adminUser' disposedBy}}
+ {{avatar disposedBy imageSize="small"}}
+ {{/link-to}}
+ |
+
+ {{date disposedAt}}
+ |
+
+ {{disposition}}
+ {{#if tookAction}}
+
+ {{/if}}
+ |
+
+ {{/each}}
+
+
+ |
+
+
{{#if flaggedPost.topicFlagged}}
-
- |
- {{{i18n admin.flags.topic_flagged}}} |
- |
+
|
+
+
+ {{{i18n admin.flags.topic_flagged}}}
+
+ |
{{/if}}
- {{#each flaggedPost.messages}}
-
+ {{#each flaggedPost.conversations}}
+
|
-
+ |
- {{#unless bySystemUser}}
- {{#link-to 'adminUser' user}}{{avatar user imageSize="small"}}{{/link-to}}
- {{message}}
-
- {{else}}
- {{i18n admin.flags.system}}:
- {{message}}
- {{/unless}}
+ {{#if response}}
+
+ {{#link-to 'adminUser' response.user}}{{avatar response.user imageSize="small"}}{{/link-to}} {{{response.excerpt}}}
+
+ {{#if reply}}
+
+ {{#link-to 'adminUser' reply.user}}{{avatar reply.user imageSize="small"}}{{/link-to}} {{{reply.excerpt}}}
+ {{#if hasMore}}
+ {{i18n admin.flags.more}}
+ {{/if}}
+
+ {{/if}}
+
+
+
+ {{/if}}
|
- |
- |
{{/each}}
-
+
- {{#if adminActiveFlagsView}}
- {{#if flaggedPost.topicFlagged}}
- {{i18n admin.flags.visit_topic}}
- {{/if}}
+ {{#if adminActiveFlagsView}}
+ {{#if flaggedPost.topicFlagged}}
+ {{i18n admin.flags.visit_topic}}
+ {{/if}}
- {{#if flaggedPost.postAuthorFlagged}}
- {{#if flaggedPost.postHidden}}
-
-
+ {{#if flaggedPost.postAuthorFlagged}}
+ {{#if flaggedPost.postHidden}}
+
+ {{else}}
+
+
+ {{/if}}
+
+
{{else}}
-
-
+
{{/if}}
-
- {{#if flaggedPost.canDeleteAsSpammer}}
-
- {{/if}}
-
-
- {{else}}
-
{{/if}}
- {{/if}}
|
-
+
{{/each}}
diff --git a/app/assets/javascripts/admin/templates/modal/admin_delete_flag.js.handlebars b/app/assets/javascripts/admin/templates/modal/admin_delete_flag.js.handlebars
new file mode 100644
index 00000000000..e1711062b08
--- /dev/null
+++ b/app/assets/javascripts/admin/templates/modal/admin_delete_flag.js.handlebars
@@ -0,0 +1,5 @@
+
+
+{{#if canDeleteAsSpammer}}
+
+{{/if}}
diff --git a/app/assets/javascripts/admin/views/admin_flags_view.js b/app/assets/javascripts/admin/views/admin_flags_view.js
index 823dd4aa379..52053a53895 100644
--- a/app/assets/javascripts/admin/views/admin_flags_view.js
+++ b/app/assets/javascripts/admin/views/admin_flags_view.js
@@ -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);
+ });
+ }
+
}
});
diff --git a/app/assets/javascripts/admin/views/modals/admin_delete_flag_view.js b/app/assets/javascripts/admin/views/modals/admin_delete_flag_view.js
new file mode 100644
index 00000000000..204e7b382a1
--- /dev/null
+++ b/app/assets/javascripts/admin/views/modals/admin_delete_flag_view.js
@@ -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')
+});
diff --git a/app/assets/javascripts/discourse/components/discourse-action-history.js.es6 b/app/assets/javascripts/discourse/components/discourse-action-history.js.es6
index 6e089d90935..2451275db93 100644
--- a/app/assets/javascripts/discourse/components/discourse-action-history.js.es6
+++ b/app/assets/javascripts/discourse/components/discourse-action-history.js.es6
@@ -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("");
});
@@ -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;
}
diff --git a/app/assets/javascripts/discourse/components/topic-status.js.es6 b/app/assets/javascripts/discourse/components/topic-status.js.es6
index fdc8585877c..606471b4ee7 100644
--- a/app/assets/javascripts/discourse/components/topic-status.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-status.js.es6
@@ -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');
diff --git a/app/assets/javascripts/discourse/models/action_summary.js b/app/assets/javascripts/discourse/models/action_summary.js
index 47ddddeccd9..f1d4abb53de 100644
--- a/app/assets/javascripts/discourse/models/action_summary.js
+++ b/app/assets/javascripts/discourse/models/action_summary.js
@@ -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'),
diff --git a/app/assets/javascripts/discourse/templates/header.js.handlebars b/app/assets/javascripts/discourse/templates/header.js.handlebars
index 825856c9b57..24bc9d0e2fa 100644
--- a/app/assets/javascripts/discourse/templates/header.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/header.js.handlebars
@@ -13,20 +13,20 @@
{{#if topic.isPrivateMessage}}
{{icon envelope}}
{{/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}}
- {{{topic.fancy_title}}}
- {{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}}
+ {{{topic.fancy_title}}}
+ {{else}}
+ {{#if topic.errorLoading}}
+ {{topic.errorTitle}}
+ {{else}}
+ {{i18n topic.loading}}
+ {{/if}}
{{/if}}
- {{/if}}
diff --git a/app/assets/javascripts/discourse/templates/topic.js.handlebars b/app/assets/javascripts/discourse/templates/topic.js.handlebars
index 0fe6f9f5e31..081fd025154 100644
--- a/app/assets/javascripts/discourse/templates/topic.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/topic.js.handlebars
@@ -17,7 +17,7 @@
{{#if editingTopic}}
{{#if isPrivateMessage}}
-
+ {{icon envelope}}
{{else}}
{{category-chooser valueAttribute="id" value=newCategoryId source=category_id}}
{{/if}}
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index 676a4681000..161816b1fa5 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -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%;
-
}
}
diff --git a/app/controllers/admin/flags_controller.rb b/app/controllers/admin/flags_controller.rb
index f7747414007..cf57307258a 100644
--- a/app/controllers/admin/flags_controller.rb
+++ b/app/controllers/admin/flags_controller.rb
@@ -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
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index b7a4123a3c6..16e2d13fb8d 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -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))
diff --git a/app/controllers/post_actions_controller.rb b/app/controllers/post_actions_controller.rb
index 9a04cf0aa72..7a2fbbd0192 100644
--- a/app/controllers/post_actions_controller.rb
+++ b/app/controllers/post_actions_controller.rb
@@ -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
diff --git a/app/models/post_action.rb b/app/models/post_action.rb
index a5b5e308c7a..00e4002c7c2 100644
--- a/app/models/post_action.rb
+++ b/app/models/post_action.rb
@@ -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
#
diff --git a/app/models/post_action_type.rb b/app/models/post_action_type.rb
index 90b7f330ffc..e46928ff105 100644
--- a/app/models/post_action_type.rb
+++ b/app/models/post_action_type.rb
@@ -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
diff --git a/app/models/report.rb b/app/models/report.rb
index 3caec73f06b..bf6b04b26f9 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -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
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 71fde80df1c..d9821f83b04 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -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?
diff --git a/app/models/user.rb b/app/models/user.rb
index 6762bcfdf2b..bbeb787adde 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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
diff --git a/app/serializers/flagged_topic_serializer.rb b/app/serializers/flagged_topic_serializer.rb
new file mode 100644
index 00000000000..2e5e08b0866
--- /dev/null
+++ b/app/serializers/flagged_topic_serializer.rb
@@ -0,0 +1,10 @@
+class FlaggedTopicSerializer < ActiveModel::Serializer
+ attributes :id,
+ :title,
+ :slug,
+ :archived,
+ :closed,
+ :visible,
+ :archetype,
+ :relative_url
+end
diff --git a/app/serializers/flagged_user_serializer.rb b/app/serializers/flagged_user_serializer.rb
new file mode 100644
index 00000000000..899602b9feb
--- /dev/null
+++ b/app/serializers/flagged_user_serializer.rb
@@ -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
diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb
index 921341cf4cb..3eae60a16e3 100644
--- a/app/serializers/post_serializer.rb
+++ b/app/serializers/post_serializer.rb
@@ -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)
diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb
index 34998320690..23313c29daa 100644
--- a/app/serializers/topic_view_serializer.rb
+++ b/app/serializers/topic_view_serializer.rb
@@ -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
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 0d351cba81e..bc696866cf5 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -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 topic has been flagged."
visit_topic: "Visit the topic to take action"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index d8bebc18818..43e5c342a0a 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -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"
diff --git a/config/routes.rb b/config/routes.rb
index f87dcf0d02d..771cf52570b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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
diff --git a/db/migrate/20140721161249_add_agreed_at_and_agreed_by_id_to_post_action.rb b/db/migrate/20140721161249_add_agreed_at_and_agreed_by_id_to_post_action.rb
new file mode 100644
index 00000000000..c676a0faf5c
--- /dev/null
+++ b/db/migrate/20140721161249_add_agreed_at_and_agreed_by_id_to_post_action.rb
@@ -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
diff --git a/db/migrate/20140721162307_rename_defer_columns_on_post_action.rb b/db/migrate/20140721162307_rename_defer_columns_on_post_action.rb
new file mode 100644
index 00000000000..7a02bc139a7
--- /dev/null
+++ b/db/migrate/20140721162307_rename_defer_columns_on_post_action.rb
@@ -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
diff --git a/db/migrate/20140725172830_remove_message_from_post_action.rb b/db/migrate/20140725172830_remove_message_from_post_action.rb
new file mode 100644
index 00000000000..778d99a7f88
--- /dev/null
+++ b/db/migrate/20140725172830_remove_message_from_post_action.rb
@@ -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
diff --git a/db/migrate/20140728120708_fix_index_on_post_action.rb b/db/migrate/20140728120708_fix_index_on_post_action.rb
new file mode 100644
index 00000000000..23be8acecdd
--- /dev/null
+++ b/db/migrate/20140728120708_fix_index_on_post_action.rb
@@ -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
diff --git a/db/migrate/20140728144308_add_first_post_created_at_to_user_stat.rb b/db/migrate/20140728144308_add_first_post_created_at_to_user_stat.rb
new file mode 100644
index 00000000000..34dfca71cb5
--- /dev/null
+++ b/db/migrate/20140728144308_add_first_post_created_at_to_user_stat.rb
@@ -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
diff --git a/db/migrate/20140728152804_add_post_and_topic_counts_to_user_stat.rb b/db/migrate/20140728152804_add_post_and_topic_counts_to_user_stat.rb
new file mode 100644
index 00000000000..3152191d16b
--- /dev/null
+++ b/db/migrate/20140728152804_add_post_and_topic_counts_to_user_stat.rb
@@ -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
diff --git a/lib/flag_query.rb b/lib/flag_query.rb
index 41324f9bab0..7178bba2c90 100644
--- a/lib/flag_query.rb
+++ b/lib/flag_query.rb
@@ -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)
- < 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
diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb
index 17dcaf83d8e..5647f71ff89 100644
--- a/lib/guardian/post_guardian.rb
+++ b/lib/guardian/post_guardian.rb
@@ -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
diff --git a/lib/guardian/user_guardian.rb b/lib/guardian/user_guardian.rb
index 3e2bafd3be5..23c964e1236 100644
--- a/lib/guardian/user_guardian.rb
+++ b/lib/guardian/user_guardian.rb
@@ -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
diff --git a/lib/post_creator.rb b/lib/post_creator.rb
index ecf30b7ca91..957d5bf0cd5 100644
--- a/lib/post_creator.rb
+++ b/lib/post_creator.rb
@@ -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
diff --git a/lib/post_destroyer.rb b/lib/post_destroyer.rb
index 3f94b146e25..7ade50a8f23 100644
--- a/lib/post_destroyer.rb
+++ b/lib/post_destroyer.rb
@@ -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 = {
diff --git a/lib/post_jobs_enqueuer.rb b/lib/post_jobs_enqueuer.rb
index 219ea863fc3..fcb2f13e835 100644
--- a/lib/post_jobs_enqueuer.rb
+++ b/lib/post_jobs_enqueuer.rb
@@ -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,
diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb
index b95681aebf4..cf6d353d42d 100644
--- a/lib/post_revisor.rb
+++ b/lib/post_revisor.rb
@@ -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
diff --git a/spec/components/flag_query_spec.rb b/spec/components/flag_query_spec.rb
index 24f6ef6772c..a6a1ac3f9b7 100644
--- a/spec/components/flag_query_spec.rb
+++ b/spec/components/flag_query_spec.rb
@@ -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
diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb
index 639dd5b6310..9af5d7f8032 100644
--- a/spec/components/guardian_spec.rb
+++ b/spec/components/guardian_spec.rb
@@ -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"
diff --git a/spec/components/post_destroyer_spec.rb b/spec/components/post_destroyer_spec.rb
index ab68f35a459..609e0040b81 100644
--- a/spec/components/post_destroyer_spec.rb
+++ b/spec/components/post_destroyer_spec.rb
@@ -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
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index ba4dcc65fe8..8831e660bf9 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -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
diff --git a/spec/controllers/post_actions_controller_spec.rb b/spec/controllers/post_actions_controller_spec.rb
index aa81cfc2d76..71ba8a6939f 100644
--- a/spec/controllers/post_actions_controller_spec.rb
+++ b/spec/controllers/post_actions_controller_spec.rb
@@ -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
diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb
index 587755e05b1..4427be8f419 100644
--- a/spec/models/post_action_spec.rb
+++ b/spec/models/post_action_spec.rb
@@ -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
diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb
index ca1b5676d68..4fd096b0381 100644
--- a/spec/models/topic_spec.rb
+++ b/spec/models/topic_spec.rb
@@ -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) }
diff --git a/spec/services/user_destroyer_spec.rb b/spec/services/user_destroyer_spec.rb
index 1d1d10db452..bf26b55f760 100644
--- a/spec/services/user_destroyer_spec.rb
+++ b/spec/services/user_destroyer_spec.rb
@@ -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 }