From b0bfc1f93f18b19143a59eafd6d8d2e84a872dcc Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 5 Sep 2014 15:40:25 -0400 Subject: [PATCH] FEATURE: Can create warnings for users via PM --- .../admin/templates/user_index.js.handlebars | 4 ++ .../components/notification-item.js.es6 | 2 +- .../discourse/components/topic-status.js.es6 | 5 ++- .../discourse/controllers/composer.js.es6 | 17 ++++++++ .../discourse/controllers/header.js.es6 | 4 ++ .../javascripts/discourse/models/_post.js | 1 + .../javascripts/discourse/models/composer.js | 1 + .../templates/composer.js.handlebars | 8 ++++ .../discourse/templates/header.js.handlebars | 2 +- .../discourse/templates/topic.js.handlebars | 4 +- .../templates/user/user.js.handlebars | 3 ++ .../javascripts/discourse/views/topic.js.es6 | 1 + .../stylesheets/common/base/_topic-list.scss | 2 +- .../stylesheets/common/base/header.scss | 8 ++++ app/assets/stylesheets/desktop/compose.scss | 9 +++++ app/assets/stylesheets/desktop/discourse.scss | 4 ++ .../stylesheets/desktop/topic-post.scss | 1 + app/assets/stylesheets/desktop/topic.scss | 2 +- app/assets/stylesheets/desktop/user.scss | 3 ++ app/controllers/posts_controller.rb | 17 ++++++-- app/models/notification.rb | 2 +- app/models/topic.rb | 1 + app/models/user.rb | 5 +++ app/models/warning.rb | 5 +++ .../admin_detailed_user_serializer.rb | 3 +- app/serializers/listable_topic_serializer.rb | 9 +++++ app/serializers/notification_serializer.rb | 11 ++++- app/serializers/topic_view_serializer.rb | 12 +++++- app/serializers/user_serializer.rb | 7 +++- config/locales/client.en.yml | 5 +++ config/locales/server.en.yml | 3 ++ db/migrate/20140905171733_create_warnings.rb | 12 ++++++ lib/post_creator.rb | 1 + lib/topic_creator.rb | 26 +++++++++++- lib/topic_query.rb | 2 +- spec/components/post_creator_spec.rb | 40 +++++++++++++++++++ spec/controllers/posts_controller_spec.rb | 19 +++++++++ 37 files changed, 243 insertions(+), 18 deletions(-) create mode 100644 app/models/warning.rb create mode 100644 db/migrate/20140905171733_create_warnings.rb diff --git a/app/assets/javascripts/admin/templates/user_index.js.handlebars b/app/assets/javascripts/admin/templates/user_index.js.handlebars index 952fd8f94..5a6ccc6c7 100644 --- a/app/assets/javascripts/admin/templates/user_index.js.handlebars +++ b/app/assets/javascripts/admin/templates/user_index.js.handlebars @@ -354,6 +354,10 @@
{{i18n admin.user.posts_read_count}}
{{posts_read_count}}
+
+
{{i18n admin.user.warnings_received_count}}
+
{{warnings_received_count}}
+
{{i18n admin.user.flags_given_received_count}}
{{flags_given_count}} / {{flags_received_count}}
diff --git a/app/assets/javascripts/discourse/components/notification-item.js.es6 b/app/assets/javascripts/discourse/components/notification-item.js.es6 index c587e12b6..4942a90ca 100644 --- a/app/assets/javascripts/discourse/components/notification-item.js.es6 +++ b/app/assets/javascripts/discourse/components/notification-item.js.es6 @@ -1,6 +1,6 @@ export default Ember.Component.extend({ tagName: 'li', - classNameBindings: ['notification.read'], + classNameBindings: ['notification.read', 'notification.is_warning'], _markRead: function(){ var self = this; diff --git a/app/assets/javascripts/discourse/components/topic-status.js.es6 b/app/assets/javascripts/discourse/components/topic-status.js.es6 index 83ce68493..fe99221da 100644 --- a/app/assets/javascripts/discourse/components/topic-status.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-status.js.es6 @@ -9,8 +9,8 @@ export default Ember.Component.extend({ classNames: ['topic-statuses'], - hasDisplayableStatus: Em.computed.or('topic.archived','topic.closed', 'topic.pinned', 'topic.unpinned', 'topic.invisible', 'topic.archetypeObject.notDefault'), - shouldRerender: Discourse.View.renderIfChanged('topic.archived','topic.closed', 'topic.pinned', 'topic.visible', 'topic.unpinned'), + hasDisplayableStatus: Em.computed.or('topic.archived','topic.closed', 'topic.pinned', 'topic.unpinned', 'topic.invisible', 'topic.archetypeObject.notDefault', 'topic.is_warning'), + shouldRerender: Discourse.View.renderIfChanged('topic.archived', 'topic.closed', 'topic.pinned', 'topic.visible', 'topic.unpinned', 'topic.is_warning'), didInsertElement: function(){ var self = this; @@ -52,6 +52,7 @@ export default Ember.Component.extend({ // Allow a plugin to add a custom icon to a topic this.trigger('addCustomIcon', buffer); + renderIconIf('topic.is_warning', 'envelope', 'warning'); renderIconIf('topic.closed', 'lock', 'locked'); renderIconIf('topic.archived', 'lock', 'archived'); renderIconIf('topic.pinned', 'thumb-tack', 'pinned', self.get("canAct") ); diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 9567ba57a..702df16a9 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -15,6 +15,16 @@ export default DiscourseController.extend({ this.set('similarTopics', []); }.on('init'), + showWarning: function() { + var usernames = this.get('model.targetUsernames'); + + // We need exactly one user to issue a warning + if (Em.empty(usernames) || usernames.split(',').length !== 1) { + return false; + } + return this.get('model.creatingPrivateMessage'); + }.property('model.creatingPrivateMessage', 'model.targetUsernames'), + actions: { // Toggle the reply view toggle: function() { @@ -103,6 +113,12 @@ export default DiscourseController.extend({ var composer = this.get('model'), self = this; + + // Clear the warning state if we're not showing the checkbox anymore + if (!this.get('showWarning')) { + this.set('model.isWarning', false); + } + if(composer.get('cantSubmitPost')) { var now = Date.now(); this.setProperties({ @@ -345,6 +361,7 @@ export default DiscourseController.extend({ this.set('model', composerModel); composerModel.set('composeState', Discourse.Composer.OPEN); + composerModel.set('isWarning', false); var composerMessages = this.get('controllers.composer-messages'); composerMessages.queryFor(composerModel); diff --git a/app/assets/javascripts/discourse/controllers/header.js.es6 b/app/assets/javascripts/discourse/controllers/header.js.es6 index 8701149d9..d7cebb23f 100644 --- a/app/assets/javascripts/discourse/controllers/header.js.es6 +++ b/app/assets/javascripts/discourse/controllers/header.js.es6 @@ -10,6 +10,10 @@ export default DiscourseController.extend({ loginRequired: Em.computed.alias('controllers.application.loginRequired'), canSignUp: Em.computed.alias('controllers.application.canSignUp'), + showPrivateMessageGlyph: function() { + return !this.get('topic.is_warning') && this.get('topic.isPrivateMessage'); + }.property('topic.is_warning', 'topic.isPrivateMessage'), + showSignUpButton: function() { return this.get('canSignUp') && !this.get('showExtraInfo'); }.property('canSignUp', 'showExtraInfo'), diff --git a/app/assets/javascripts/discourse/models/_post.js b/app/assets/javascripts/discourse/models/_post.js index 6fac98173..f2980e50d 100644 --- a/app/assets/javascripts/discourse/models/_post.js +++ b/app/assets/javascripts/discourse/models/_post.js @@ -149,6 +149,7 @@ Discourse.Post = Discourse.Model.extend({ var data = { raw: this.get('raw'), topic_id: this.get('topic_id'), + is_warning: this.get('is_warning'), reply_to_post_number: this.get('reply_to_post_number'), category: this.get('category'), archetype: this.get('archetype'), diff --git a/app/assets/javascripts/discourse/models/composer.js b/app/assets/javascripts/discourse/models/composer.js index 9cee7aa00..9709b5529 100644 --- a/app/assets/javascripts/discourse/models/composer.js +++ b/app/assets/javascripts/discourse/models/composer.js @@ -494,6 +494,7 @@ Discourse.Composer = Discourse.Model.extend({ title: this.get('title'), category: this.get('categoryId'), topic_id: this.get('topic.id'), + is_warning: this.get('isWarning'), imageSizes: opts.imageSizes, cooked: this.getCookedHtml(), reply_count: 0, diff --git a/app/assets/javascripts/discourse/templates/composer.js.handlebars b/app/assets/javascripts/discourse/templates/composer.js.handlebars index 322ab1feb..a907c3246 100644 --- a/app/assets/javascripts/discourse/templates/composer.js.handlebars +++ b/app/assets/javascripts/discourse/templates/composer.js.handlebars @@ -35,6 +35,14 @@ placeholderKey="composer.users_placeholder" tabindex="1" usernames=model.targetUsernames}} + {{#if showWarning}} +
+ +
+ {{/if}} {{/if}}
diff --git a/app/assets/javascripts/discourse/templates/header.js.handlebars b/app/assets/javascripts/discourse/templates/header.js.handlebars index 06d50aa81..179869774 100644 --- a/app/assets/javascripts/discourse/templates/header.js.handlebars +++ b/app/assets/javascripts/discourse/templates/header.js.handlebars @@ -10,7 +10,7 @@ {{/if}}

- {{#if topic.isPrivateMessage}} + {{#if showPrivateMessageGlyph}} {{icon envelope}} {{/if}} {{#if topic.category.parentCategory}} diff --git a/app/assets/javascripts/discourse/templates/topic.js.handlebars b/app/assets/javascripts/discourse/templates/topic.js.handlebars index abf03ae6c..ecf758060 100644 --- a/app/assets/javascripts/discourse/templates/topic.js.handlebars +++ b/app/assets/javascripts/discourse/templates/topic.js.handlebars @@ -27,7 +27,9 @@ {{else}}

- + {{#unless is_warning}} + + {{/unless}} {{#unless isPrivateMessage}} {{#if category.parentCategory}} {{bound-category-link category.parentCategory}} diff --git a/app/assets/javascripts/discourse/templates/user/user.js.handlebars b/app/assets/javascripts/discourse/templates/user/user.js.handlebars index 46d431815..509424f6c 100644 --- a/app/assets/javascripts/discourse/templates/user/user.js.handlebars +++ b/app/assets/javascripts/discourse/templates/user/user.js.handlebars @@ -80,6 +80,9 @@ {{#if number_of_suspensions}}
{{number_of_suspensions}} {{i18n user.staff_counters.suspensions}}
{{/if}} + {{#if number_of_warnings}} +
{{number_of_warnings}} {{i18n user.staff_counters.warnings_received}}
+ {{/if}}

{{bound-avatar model "huge"}} diff --git a/app/assets/javascripts/discourse/views/topic.js.es6 b/app/assets/javascripts/discourse/views/topic.js.es6 index a15125343..c09130113 100644 --- a/app/assets/javascripts/discourse/views/topic.js.es6 +++ b/app/assets/javascripts/discourse/views/topic.js.es6 @@ -6,6 +6,7 @@ export default Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, { userFiltersBinding: 'controller.userFilters', classNameBindings: ['controller.multiSelect:multi-select', 'topic.archetype', + 'topic.is_warning', 'topic.category.read_restricted:read_restricted', 'topic.deleted:deleted-topic', 'topic.categoryClass'], diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index 92a740058..26e0a4ac3 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -308,4 +308,4 @@ ol.category-breadcrumb { div.education { color: scale-color($primary, $lightness: 50%); -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index a6121425f..c3342aeb3 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -208,6 +208,14 @@ overflow: hidden; } } + .is-warning { + i.fa-envelope-o { + &:before { + content: "\f0e0"; + } + color: $danger; + } + } .read { background-color: $secondary; } diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 6d7535e29..9cd2125e8 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -3,6 +3,15 @@ // hack, this needs to be done cleaner #private-message-users { width: 400px; + float: left; +} + +.add-warning { + width: 300px; + display: inline-block; + position: relative; + top: -30px; + margin-left: 20px; } .composer-popup-container { diff --git a/app/assets/stylesheets/desktop/discourse.scss b/app/assets/stylesheets/desktop/discourse.scss index 13b422b74..015e5dade 100644 --- a/app/assets/stylesheets/desktop/discourse.scss +++ b/app/assets/stylesheets/desktop/discourse.scss @@ -125,6 +125,10 @@ body { font-size: 15px; } } + + i.fa-envelope { + color: $danger; + } } /* bootstrap carryover */ diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index d8653437a..5b0d6e789 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -527,6 +527,7 @@ iframe { .topic-statuses { i { color: $header_primary; } + i.fa-envelope { color: $danger; } .unpinned { color: $header_primary; } } .topic-link { color: $header_primary; diff --git a/app/assets/stylesheets/desktop/topic.scss b/app/assets/stylesheets/desktop/topic.scss index 4edabbde4..785f8f923 100644 --- a/app/assets/stylesheets/desktop/topic.scss +++ b/app/assets/stylesheets/desktop/topic.scss @@ -65,7 +65,7 @@ color: scale-color($primary, $lightness: 75%); float: left; margin: 0 5px 0 0; - } +} .private_message #topic-title .private-message-glyph { display: inline; } a.reply-new { diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 35f372a27..fe6f71443 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -395,6 +395,9 @@ .flagged-posts { background-color: #E49735; } + .warnings-received { + background-color: #EC441B; + } .deleted-posts { background-color: #EC441B; } diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 86a57e216..8febcf796 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -331,12 +331,21 @@ class PostsController < ApplicationController permitted << :embed_url end + params.require(:raw) - params.permit(*permitted).tap do |whitelisted| - whitelisted[:image_sizes] = params[:image_sizes] - # TODO this does not feel right, we should name what meta_data is allowed - whitelisted[:meta_data] = params[:meta_data] + result = params.permit(*permitted).tap do |whitelisted| + whitelisted[:image_sizes] = params[:image_sizes] + # TODO this does not feel right, we should name what meta_data is allowed + whitelisted[:meta_data] = params[:meta_data] end + + # Staff are allowed to pass `is_warning` + if current_user.staff? + params.permit(:is_warning) + result[:is_warning] = (params[:is_warning] == "true") + end + + result end def too_late_to(action, post) diff --git a/app/models/notification.rb b/app/models/notification.rb index 55ebdcae6..c5f6eb7ec 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -98,7 +98,7 @@ class Notification < ActiveRecord::Base def self.recent_report(user, count = nil) count ||= 10 - notifications = user.notifications.recent(count).includes(:topic).to_a + notifications = user.notifications.recent(count).includes({:topic => :warning}).to_a if notifications.present? notifications += user.notifications diff --git a/app/models/topic.rb b/app/models/topic.rb index 8e9533fd4..9b35c05de 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -100,6 +100,7 @@ class Topic < ActiveRecord::Base has_many :invites, through: :topic_invites, source: :invite has_many :revisions, foreign_key: :topic_id, class_name: 'TopicRevision' + has_one :warning # When we want to temporarily attach some data to a forum topic (usually before serialization) attr_accessor :user_data diff --git a/app/models/user.rb b/app/models/user.rb index 59b73c735..d07a02123 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -35,6 +35,7 @@ class User < ActiveRecord::Base has_many :invites, dependent: :destroy has_many :topic_links, dependent: :destroy has_many :uploads + has_many :warnings has_one :user_avatar, dependent: :destroy has_one :facebook_user_info, dependent: :destroy @@ -393,6 +394,10 @@ class User < ActiveRecord::Base PostAction.where(user_id: id, post_action_type_id: PostActionType.flag_types.values).count end + def warnings_received_count + warnings.count + end + def flags_received_count posts.includes(:post_actions).where('post_actions.post_action_type_id' => PostActionType.flag_types.values).count end diff --git a/app/models/warning.rb b/app/models/warning.rb new file mode 100644 index 000000000..7d60d2deb --- /dev/null +++ b/app/models/warning.rb @@ -0,0 +1,5 @@ +class Warning < ActiveRecord::Base + belongs_to :user + belongs_to :topic + belongs_to :created_by, class_name: 'User' +end diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb index b24d2d126..1df1089ef 100644 --- a/app/serializers/admin_detailed_user_serializer.rb +++ b/app/serializers/admin_detailed_user_serializer.rb @@ -17,7 +17,8 @@ class AdminDetailedUserSerializer < AdminUserSerializer :can_be_deleted, :suspend_reason, :primary_group_id, - :badge_count + :badge_count, + :warnings_received_count has_one :approved_by, serializer: BasicUserSerializer, embed: :objects has_one :api_key, serializer: ApiKeySerializer, embed: :objects diff --git a/app/serializers/listable_topic_serializer.rb b/app/serializers/listable_topic_serializer.rb index b89c05aea..99f2aff8b 100644 --- a/app/serializers/listable_topic_serializer.rb +++ b/app/serializers/listable_topic_serializer.rb @@ -19,6 +19,7 @@ class ListableTopicSerializer < BasicTopicSerializer :visible, :closed, :archived, + :is_warning, :notification_level has_one :last_poster, serializer: BasicUserSerializer, embed: :objects @@ -37,6 +38,14 @@ class ListableTopicSerializer < BasicTopicSerializer false end + def is_warning + object.private_message? && object.warning.present? + end + + def include_is_warning? + is_warning + end + def unseen !seen end diff --git a/app/serializers/notification_serializer.rb b/app/serializers/notification_serializer.rb index c6eb84127..c73bfa903 100644 --- a/app/serializers/notification_serializer.rb +++ b/app/serializers/notification_serializer.rb @@ -6,12 +6,21 @@ class NotificationSerializer < ApplicationSerializer :post_number, :topic_id, :slug, - :data + :data, + :is_warning def slug Slug.for(object.topic.title) if object.topic.present? end + def is_warning + object.topic.present? && object.topic.warning.present? + end + + def include_is_warning? + is_warning + end + def data object.data_hash end diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index 23313c29d..b9d3a4737 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -41,7 +41,9 @@ class TopicViewSerializer < ApplicationSerializer :deleted_by, :has_deleted, :actions_summary, - :expandable_first_post + :expandable_first_post, + :is_warning + # Define a delegator for each attribute of the topic we want attributes(*topic_attributes) @@ -109,6 +111,14 @@ class TopicViewSerializer < ApplicationSerializer result end + + def is_warning + object.topic.private_message? && object.topic.warning.present? + end + + def include_is_warning? + is_warning + end def draft object.draft end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 15b871a8a..1edb4034d 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -55,7 +55,8 @@ class UserSerializer < BasicUserSerializer staff_attributes :number_of_deleted_posts, :number_of_flagged_posts, :number_of_flags_given, - :number_of_suspensions + :number_of_suspensions, + :number_of_warnings private_attributes :email, @@ -193,6 +194,10 @@ class UserSerializer < BasicUserSerializer .count end + def number_of_warnings + object.warnings.count + end + def number_of_suspensions UserHistory.for(object, :suspend_user).count end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 220c34c3c..ecddbc6db 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -336,6 +336,7 @@ en: flagged_posts: "flagged posts" deleted_posts: "deleted posts" suspensions: "suspensions" + warnings_received: "warnings received" messages: all: "All" @@ -624,6 +625,7 @@ en: message: "Authenticating with GitHub (make sure pop up blockers are not enabled)" composer: + add_warning: "This is an official warning." posting_not_on_topic: "Which topic do you want to reply to?" saving_draft_tip: "saving" saved_draft_tip: "saved" @@ -1318,6 +1320,8 @@ en: other: "%{count} clicks" topic_statuses: + warning: + help: "This is an official warning." locked: help: "This topic is closed; it no longer accepts new replies" archived: @@ -1898,6 +1902,7 @@ en: topics_entered: Topics Viewed flags_given_count: Flags Given flags_received_count: Flags Received + warnings_received_count: Warnings Received flags_given_received_count: 'Flags Given / Received' approve: 'Approve' approved_by: "approved by" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 639ce4097..1f7d17923 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -211,6 +211,9 @@ en: models: topic: attributes: + base: + warning_requires_pm: "You can only attach warnings to private messages." + too_many_users: "You can only send warnings to one user at a time." archetype: cant_send_pm: "Sorry, you cannot send a private message to that user." user: diff --git a/db/migrate/20140905171733_create_warnings.rb b/db/migrate/20140905171733_create_warnings.rb new file mode 100644 index 000000000..90d38725b --- /dev/null +++ b/db/migrate/20140905171733_create_warnings.rb @@ -0,0 +1,12 @@ +class CreateWarnings < ActiveRecord::Migration + def change + create_table :warnings do |t| + t.references :topic, null: false + t.references :user, null: false + t.integer :created_by_id, null: false + t.timestamps + end + add_index :warnings, :user_id + add_index :warnings, :topic_id, unique: true + end +end diff --git a/lib/post_creator.rb b/lib/post_creator.rb index c4de0707c..ee777a26e 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -28,6 +28,7 @@ class PostCreator # When creating a topic: # title - New topic title # archetype - Topic archetype + # is_warning - Is the topic a warning? # category - Category to assign to topic # target_usernames - comma delimited list of usernames for membership (private message) # target_group_names - comma delimited list of groups for membership (private message) diff --git a/lib/topic_creator.rb b/lib/topic_creator.rb index 9209ec85c..cf25144b1 100644 --- a/lib/topic_creator.rb +++ b/lib/topic_creator.rb @@ -10,6 +10,7 @@ class TopicCreator @user = user @guardian = guardian @opts = opts + @added_users = [] end def create @@ -18,6 +19,7 @@ class TopicCreator setup_auto_close_time process_private_message save_topic + create_warning watch_topic @topic @@ -25,6 +27,27 @@ class TopicCreator private + def create_warning + return unless @opts[:is_warning] + + # We can only attach warnings to PMs + unless @topic.private_message? + @topic.errors.add(:base, :warning_requires_pm) + @errors = @topic.errors + raise ActiveRecord::Rollback.new + end + + # Don't create it if there is more than one user + if @added_users.size != 1 + @topic.errors.add(:base, :too_many_users) + @errors = @topic.errors + raise ActiveRecord::Rollback.new + end + + # Create a warning record + Warning.create(topic: @topic, user: @added_users.first, created_by: @user) + end + def watch_topic unless @opts[:auto_track] == false @topic.notifier.watch_topic!(@topic.user_id) @@ -108,7 +131,8 @@ class TopicCreator def add_users(topic, usernames) return unless usernames User.where(username: usernames.split(',')).each do |user| - check_can_send_permission!(topic,user) + check_can_send_permission!(topic, user) + @added_users << user topic.topic_allowed_users.build(user_id: user.id) end end diff --git a/lib/topic_query.rb b/lib/topic_query.rb index ceb341ba5..022806db3 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -165,7 +165,7 @@ class TopicQuery options.reverse_merge!(per_page: SiteSetting.topics_per_page) # Start with a list of all topics - result = Topic.includes(:allowed_users) + result = Topic.includes(:allowed_users).includes(:warning) .where("topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{user.id.to_i})") .joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{user.id.to_i})") .order(TopicQuerySQL.order_nocategory_basic_bumped) diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb index 7b02d7747..bf7ee76bc 100644 --- a/spec/components/post_creator_spec.rb +++ b/spec/components/post_creator_spec.rb @@ -350,6 +350,9 @@ describe PostCreator do end it 'acts correctly' do + # It's not a warning + post.topic.warning.should be_blank + post.topic.archetype.should == Archetype.private_message post.topic.topic_allowed_users.count.should == 3 @@ -370,6 +373,43 @@ describe PostCreator do end end + context "warnings" do + let(:target_user1) { Fabricate(:coding_horror) } + let(:target_user2) { Fabricate(:moderator) } + let(:base_args) do + { title: 'you need a warning buddy!', + raw: "you did something bad and I'm telling you about it!", + is_warning: true, + target_usernames: target_user1.username, + category: 1 } + end + + it "works as expected" do + # Invalid archetype + creator = PostCreator.new(user, base_args) + creator.create + creator.errors.should be_present + + # Too many users + creator = PostCreator.new(user, base_args.merge(archetype: Archetype.private_message, + target_usernames: [target_user1.username, target_user2.username].join(','))) + creator.create + creator.errors.should be_present + + # Success + creator = PostCreator.new(user, base_args.merge(archetype: Archetype.private_message)) + post = creator.create + creator.errors.should be_blank + + topic = post.topic + topic.should be_present + topic.warning.should be_present + topic.warning.user.should == target_user1 + topic.warning.created_by.should == user + target_user1.warnings.count.should == 1 + end + end + context 'private message to group' do let(:target_user1) { Fabricate(:coding_horror) } let(:target_user2) { Fabricate(:moderator) } diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index 23fde8ccd..9fe83fec4 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -384,6 +384,7 @@ describe PostsController do describe 'when logged in' do let!(:user) { log_in } + let(:moderator) { log_in(:moderator) } let(:new_post) { Fabricate.build(:post, user: user) } it "raises an exception without a raw parameter" do @@ -492,6 +493,24 @@ describe PostsController do xhr :post, :create, {raw: 'hello', meta_data: {xyz: 'abc'}} end + context "is_warning" do + it "doesn't pass `is_warning` through if you're not staff" do + PostCreator.expects(:new).with(user, Not(has_entries('is_warning' => true))).returns(post_creator) + xhr :post, :create, {raw: 'hello', archetype: 'private_message', is_warning: 'true'} + end + + it "passes `is_warning` through if you're staff" do + PostCreator.expects(:new).with(moderator, has_entries('is_warning' => true)).returns(post_creator) + xhr :post, :create, {raw: 'hello', archetype: 'private_message', is_warning: 'true'} + end + + it "passes `is_warning` as false through if you're staff" do + PostCreator.expects(:new).with(moderator, has_entries('is_warning' => false)).returns(post_creator) + xhr :post, :create, {raw: 'hello', archetype: 'private_message', is_warning: 'false'} + end + + end + end end