diff --git a/app/assets/javascripts/discourse/app/controllers/composer.js b/app/assets/javascripts/discourse/app/controllers/composer.js index 04f4fcf47bd..e0fbdbf3964 100644 --- a/app/assets/javascripts/discourse/app/controllers/composer.js +++ b/app/assets/javascripts/discourse/app/controllers/composer.js @@ -233,6 +233,7 @@ export default Controller.extend({ }, isStaffUser: reads("currentUser.staff"), + whisperer: reads("currentUser.whisperer"), canUnlistTopic: and("model.creatingTopic", "isStaffUser"), @@ -289,12 +290,12 @@ export default Controller.extend({ return SAVE_LABELS[modelAction]; }, - @discourseComputed("isStaffUser", "model.action") - canWhisper(isStaffUser, modelAction) { + @discourseComputed("whisperer", "model.action") + canWhisper(whisperer, modelAction) { return ( this.siteSettings.enable_whispers && - isStaffUser && - Composer.REPLY === modelAction + Composer.REPLY === modelAction && + whisperer ); }, diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js index 99635b86c79..df89002a95b 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js @@ -19,7 +19,11 @@ import userFixtures from "discourse/tests/fixtures/user-fixtures"; import { cloneJSON } from "discourse-common/lib/object"; acceptance("Composer Actions", function (needs) { - needs.user(); + needs.user({ + id: 5, + username: "kris", + whisperer: true, + }); needs.settings({ prioritize_username_in_ux: true, display_name_on_post: false, @@ -78,7 +82,8 @@ acceptance("Composer Actions", function (needs) { ); }); - test("replying to post - toggle_whisper", async function (assert) { + test("replying to post - toggle_whisper for whisperers", async function (assert) { + updateCurrentUser({ admin: false, moderator: false }); const composerActions = selectKit(".composer-actions"); await visit("/t/internationalization-localization/280"); @@ -346,7 +351,13 @@ acceptance("Composer Actions", function (needs) { test("replying to post as TL3 user", async function (assert) { const composerActions = selectKit(".composer-actions"); - updateCurrentUser({ moderator: false, admin: false, trust_level: 3 }); + updateCurrentUser({ + moderator: false, + admin: false, + trust_level: 3, + whisperer: false, + groups: [{ id: 13, name: "tl3_group" }], + }); await visit("/t/internationalization-localization/280"); await click("article#post_3 button.reply"); await composerActions.expand(); @@ -364,7 +375,13 @@ acceptance("Composer Actions", function (needs) { test("replying to post as TL4 user", async function (assert) { const composerActions = selectKit(".composer-actions"); - updateCurrentUser({ moderator: false, admin: false, trust_level: 4 }); + updateCurrentUser({ + moderator: false, + admin: false, + trust_level: 4, + whisperer: false, + groups: [{ id: 13, name: "tl4_group" }], + }); await visit("/t/internationalization-localization/280"); await click("article#post_3 button.reply"); await composerActions.expand(); diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js index 018bdf7b161..9327d4e6f1c 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js @@ -26,8 +26,14 @@ import { Promise } from "rsvp"; import sinon from "sinon"; acceptance("Composer", function (needs) { - needs.user(); - needs.settings({ enable_whispers: true }); + needs.user({ + id: 5, + username: "kris", + whisperer: true, + }); + needs.settings({ + enable_whispers: true, + }); needs.site({ can_tag_topics: true }); needs.pretender((server, helper) => { server.post("/uploads/lookup-urls", () => { @@ -455,7 +461,7 @@ acceptance("Composer", function (needs) { ); }); - test("Composer can toggle whispers", async function (assert) { + test("Composer can toggle whispers when whisperer user", async function (assert) { const menu = selectKit(".toolbar-popup-menu-options"); await visit("/t/this-is-a-test-topic/9"); diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index d30a1981e02..909f540af1f 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -1257,7 +1257,7 @@ class TopicsController < ApplicationController topic_query.options[:limit] = false topics = topic_query.filter_private_messages_unread(current_user, filter) else - topics = TopicQuery.unread_filter(topic_query.joined_topic_user, staff: guardian.is_staff?).listable_topics + topics = TopicQuery.unread_filter(topic_query.joined_topic_user, whisperer: guardian.is_whisperer?).listable_topics topics = TopicQuery.tracked_filter(topics, current_user.id) if params[:tracked].to_s == "true" if params[:category_id] diff --git a/app/models/concerns/roleable.rb b/app/models/concerns/roleable.rb index cba29f7ca19..4a9a6be3ddb 100644 --- a/app/models/concerns/roleable.rb +++ b/app/models/concerns/roleable.rb @@ -18,6 +18,17 @@ module Roleable !staff? end + def whisperer? + @whisperer ||= begin + return false if !SiteSetting.enable_whispers? + return true if staff? + whispers_allowed_group_ids = SiteSetting.whispers_allowed_group_ids + return false if whispers_allowed_group_ids.blank? + return true if whispers_allowed_group_ids.include?(primary_group_id) + group_users&.exists?(group_id: whispers_allowed_group_ids) + end + end + def grant_moderation! return if moderator set_permission('moderator', true) @@ -61,6 +72,11 @@ module Roleable end end + def reload(options = nil) + @whisperer = nil + super(options) + end + private def auto_approve_user diff --git a/app/models/concerns/topic_tracking_state_publishable.rb b/app/models/concerns/topic_tracking_state_publishable.rb index a6bb7dd33a0..77dd75a841a 100644 --- a/app/models/concerns/topic_tracking_state_publishable.rb +++ b/app/models/concerns/topic_tracking_state_publishable.rb @@ -12,7 +12,7 @@ module TopicTrackingStatePublishable notification_level: nil) highest_post_number = DB.query_single( - "SELECT #{user.staff? ? "highest_staff_post_number" : "highest_post_number"} FROM topics WHERE id = ?", + "SELECT #{user.whisperer? ? "highest_staff_post_number" : "highest_post_number"} FROM topics WHERE id = ?", topic_id ).first diff --git a/app/models/group_user.rb b/app/models/group_user.rb index c7277e50c8f..babfaade4e6 100644 --- a/app/models/group_user.rb +++ b/app/models/group_user.rb @@ -24,7 +24,9 @@ class GroupUser < ActiveRecord::Base end def self.update_first_unread_pm(last_seen, limit: 10_000) - DB.exec(<<~SQL, archetype: Archetype.private_message, last_seen: last_seen, limit: limit, now: 10.minutes.ago) + whisperers_group_ids = SiteSetting.whispers_allowed_group_ids + + DB.exec(<<~SQL, archetype: Archetype.private_message, last_seen: last_seen, limit: limit, now: 10.minutes.ago, whisperers_group_ids: whisperers_group_ids) UPDATE group_users gu SET first_unread_pm_at = Y.min_date FROM ( @@ -51,7 +53,7 @@ class GroupUser < ActiveRecord::Base WHERE t.deleted_at IS NULL AND t.archetype = :archetype AND tu.last_read_post_number < CASE - WHEN u.admin OR u.moderator + WHEN u.admin OR u.moderator #{whisperers_group_ids.present? ? 'OR gu2.group_id IN (:whisperers_group_ids)' : ''} THEN t.highest_staff_post_number ELSE t.highest_post_number END diff --git a/app/models/post_timing.rb b/app/models/post_timing.rb index 690d5e38557..9efb3e59f2a 100644 --- a/app/models/post_timing.rb +++ b/app/models/post_timing.rb @@ -60,7 +60,7 @@ class PostTiming < ActiveRecord::Base def self.destroy_last_for(user, topic_id: nil, topic: nil) topic ||= Topic.find(topic_id) - post_number = user.staff? ? topic.highest_staff_post_number : topic.highest_post_number + post_number = user.whisperer? ? topic.highest_staff_post_number : topic.highest_post_number last_read = post_number - 1 diff --git a/app/models/private_message_topic_tracking_state.rb b/app/models/private_message_topic_tracking_state.rb index 66c5003efaf..cd03054c6f1 100644 --- a/app/models/private_message_topic_tracking_state.rb +++ b/app/models/private_message_topic_tracking_state.rb @@ -61,7 +61,7 @@ class PrivateMessageTopicTrackingState SQL <<~SQL - #{TopicTrackingState.unread_filter_sql(staff: user.staff?)} + #{TopicTrackingState.unread_filter_sql(whisperer: user.whisperer?)} #{first_unread_pm_at ? "AND topics.updated_at > '#{first_unread_pm_at}'" : ""} SQL end @@ -79,7 +79,7 @@ class PrivateMessageTopicTrackingState u.id AS user_id, last_read_post_number, tu.notification_level, - #{TopicTrackingState.highest_post_number_column_select(user.staff?)}, + #{TopicTrackingState.highest_post_number_column_select(user.whisperer?)}, ARRAY(SELECT group_id FROM topic_allowed_groups WHERE topic_allowed_groups.topic_id = topics.id) AS group_ids FROM topics JOIN users u on u.id = #{user.id.to_i} diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index f8e08a7e311..b074d95af44 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -191,6 +191,14 @@ class SiteSetting < ActiveRecord::Base SiteSetting::Upload end + def self.whispers_allowed_group_ids + if SiteSetting.enable_whispers && SiteSetting.whispers_allowed_groups.present? + SiteSetting.whispers_allowed_groups.split("|").map(&:to_i) + else + [] + end + end + def self.require_invite_code invite_code.present? end diff --git a/app/models/topic.rb b/app/models/topic.rb index 007fa936aaa..c133bc734d0 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -404,7 +404,7 @@ class Topic < ActiveRecord::Base types = Post.types result = [types[:regular]] result += [types[:moderator_action], types[:small_action]] if include_moderator_actions - result << types[:whisper] if viewed_by&.staff? + result << types[:whisper] if viewed_by&.whisperer? result end diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb index eeec84c2e87..61b6903a8b1 100644 --- a/app/models/topic_tracking_state.rb +++ b/app/models/topic_tracking_state.rb @@ -65,7 +65,7 @@ class TopicTrackingState publish_read(topic.id, 1, topic.user) end - def self.publish_latest(topic, staff_only = false) + def self.publish_latest(topic, whisper = false) return unless topic.regular? tag_ids, tags = nil @@ -89,8 +89,8 @@ class TopicTrackingState end group_ids = - if staff_only - [Group::AUTO_GROUPS[:staff]] + if whisper + [Group::AUTO_GROUPS[:staff], *SiteSetting.whispers_allowed_group_ids] else topic.category && topic.category.secure_group_ids end @@ -151,7 +151,7 @@ class TopicTrackingState group_ids = if post.post_type == Post.types[:whisper] - [Group::AUTO_GROUPS[:staff]] + [Group::AUTO_GROUPS[:staff], *SiteSetting.whispers_allowed_group_ids] else post.topic.category && post.topic.category.secure_group_ids end @@ -253,8 +253,8 @@ class TopicTrackingState " AND dismissed_topic_users.id IS NULL" end - def self.unread_filter_sql(staff: false) - TopicQuery.unread_filter(Topic, staff: staff).where_clause.ast.to_sql + def self.unread_filter_sql(whisperer: false) + TopicQuery.unread_filter(Topic, whisperer: whisperer).where_clause.ast.to_sql end def self.treat_as_new_topic_clause @@ -319,6 +319,7 @@ class TopicTrackingState skip_order: true, staff: user.staff?, admin: user.admin?, + whisperer: user.whisperer?, user: user, muted_tag_ids: tag_ids ) @@ -332,6 +333,7 @@ class TopicTrackingState staff: user.staff?, filter_old_unread: true, admin: user.admin?, + whisperer: user.whisperer?, user: user, muted_tag_ids: tag_ids ) @@ -369,6 +371,7 @@ class TopicTrackingState skip_order: false, staff: false, admin: false, + whisperer: false, select: nil, custom_state_filter: nil, additional_join_sql: nil @@ -377,7 +380,7 @@ class TopicTrackingState if skip_unread "1=0" else - unread_filter_sql(staff: staff) + unread_filter_sql(whisperer: whisperer) end filter_old_unread_sql = @@ -399,7 +402,7 @@ class TopicTrackingState u.id as user_id, topics.created_at, topics.updated_at, - #{highest_post_number_column_select(staff)}, + #{highest_post_number_column_select(whisperer)}, last_read_post_number, c.id as category_id, tu.notification_level, @@ -497,8 +500,8 @@ class TopicTrackingState sql end - def self.highest_post_number_column_select(staff) - "#{staff ? "topics.highest_staff_post_number AS highest_post_number" : "topics.highest_post_number"}" + def self.highest_post_number_column_select(whisperer) + "#{whisperer ? "topics.highest_staff_post_number AS highest_post_number" : "topics.highest_post_number"}" end def self.publish_read_indicator_on_write(topic_id, last_read_post_number, user_id) diff --git a/app/models/user_stat.rb b/app/models/user_stat.rb index e7d41994d1a..e6a86c91152 100644 --- a/app/models/user_stat.rb +++ b/app/models/user_stat.rb @@ -19,7 +19,9 @@ class UserStat < ActiveRecord::Base UPDATE_UNREAD_USERS_LIMIT = 10_000 def self.update_first_unread_pm(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT) - DB.exec(<<~SQL, archetype: Archetype.private_message, now: UPDATE_UNREAD_MINUTES_AGO.minutes.ago, last_seen: last_seen, limit: limit) + whisperers_group_ids = SiteSetting.whispers_allowed_group_ids + + DB.exec(<<~SQL, archetype: Archetype.private_message, now: UPDATE_UNREAD_MINUTES_AGO.minutes.ago, last_seen: last_seen, limit: limit, whisperers_group_ids: whisperers_group_ids) UPDATE user_stats us SET first_unread_pm_at = COALESCE(Z.min_date, :now) FROM ( @@ -35,10 +37,11 @@ class UserStat < ActiveRecord::Base INNER JOIN topics t ON t.id = tau.topic_id INNER JOIN users u ON u.id = tau.user_id LEFT JOIN topic_users tu ON t.id = tu.topic_id AND tu.user_id = tau.user_id + #{whisperers_group_ids.present? ? 'LEFT JOIN group_users gu ON gu.group_id IN (:whisperers_group_ids) AND gu.user_id = u.id' : ''} WHERE t.deleted_at IS NULL AND t.archetype = :archetype AND tu.last_read_post_number < CASE - WHEN u.admin OR u.moderator + WHEN u.admin OR u.moderator #{whisperers_group_ids.present? ? 'OR gu.id IS NOT NULL' : ''} THEN t.highest_staff_post_number ELSE t.highest_post_number END diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index afbc3b75857..23b59e70cfb 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -12,6 +12,7 @@ class CurrentUserSerializer < BasicUserSerializer :notification_channel_position, :moderator?, :staff?, + :whisperer?, :title, :any_posts, :enable_quoting, diff --git a/app/serializers/listable_topic_serializer.rb b/app/serializers/listable_topic_serializer.rb index 77a90b29e7a..afcbb6fa6c1 100644 --- a/app/serializers/listable_topic_serializer.rb +++ b/app/serializers/listable_topic_serializer.rb @@ -54,7 +54,7 @@ class ListableTopicSerializer < BasicTopicSerializer end def highest_post_number - (scope.is_staff? && object.highest_staff_post_number) || object.highest_post_number + (scope.is_whisperer? && object.highest_staff_post_number) || object.highest_post_number end def liked diff --git a/app/serializers/user_post_topic_bookmark_base_serializer.rb b/app/serializers/user_post_topic_bookmark_base_serializer.rb index e62d82e670f..b91002a4b1e 100644 --- a/app/serializers/user_post_topic_bookmark_base_serializer.rb +++ b/app/serializers/user_post_topic_bookmark_base_serializer.rb @@ -48,7 +48,7 @@ class UserPostTopicBookmarkBaseSerializer < UserBookmarkBaseSerializer end def highest_post_number - scope.is_staff? ? topic.highest_staff_post_number : topic.highest_post_number + scope.is_whisperer? ? topic.highest_staff_post_number : topic.highest_post_number end def last_read_post_number diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index d1356341edd..3e40272aa7e 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1683,6 +1683,7 @@ en: enable_badges: "Enable the badge system" max_favorite_badges: "Maximum number of badges that user can select" enable_whispers: "Allow staff private communication within topics." + whispers_allowed_groups: "Allow private communication within topics for members of specified groups." allow_index_in_robots_txt: "Specify in robots.txt that this site is allowed to be indexed by web search engines. In exceptional cases you can permanently override robots.txt." blocked_email_domains: "A pipe-delimited list of email domains that users are not allowed to register accounts with. Example: mailinator.com|trashmail.net" diff --git a/config/site_settings.yml b/config/site_settings.yml index 2e848aefab3..6e32ad7c37f 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -323,6 +323,13 @@ basic: enable_whispers: client: true default: false + whispers_allowed_groups: + client: true + type: group_list + list_type: compact + default: "" + allow_any: false + refresh: true enable_bookmarks_with_reminders: client: true default: true diff --git a/lib/guardian.rb b/lib/guardian.rb index 43ac22b0bf2..a285656725d 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -56,6 +56,9 @@ class Guardian def topic_create_allowed_category_ids [] end + def groups + [] + end def has_trust_level?(level) false end @@ -65,6 +68,9 @@ class Guardian def email nil end + def whisperer? + false + end end attr_reader :request @@ -99,6 +105,10 @@ class Guardian @user.moderator? end + def is_whisperer? + @user.whisperer? + end + def is_category_group_moderator?(category) return false unless category return false unless authenticated? diff --git a/lib/guardian/topic_guardian.rb b/lib/guardian/topic_guardian.rb index b8cdcc76e8a..f5cff4eefc8 100644 --- a/lib/guardian/topic_guardian.rb +++ b/lib/guardian/topic_guardian.rb @@ -36,11 +36,11 @@ module TopicGuardian end def can_create_whisper? - is_staff? && SiteSetting.enable_whispers? + @user.whisperer? end - def can_see_whispers?(_topic) - is_staff? + def can_see_whispers?(_topic = nil) + @user.whisperer? end def can_publish_topic?(topic, category) diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 56c02a167af..1be440da8d0 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -336,8 +336,8 @@ class TopicQuery .where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser.notification_levels[:tracking]) end - def self.unread_filter(list, staff: false) - col_name = staff ? "highest_staff_post_number" : "highest_post_number" + def self.unread_filter(list, whisperer: false) + col_name = whisperer ? "highest_staff_post_number" : "highest_post_number" list .where("tu.last_read_post_number < topics.#{col_name}") @@ -474,7 +474,7 @@ class TopicQuery def unseen_results(options = {}) result = default_results(options) - result = unseen_filter(result, @user.first_seen_at, @user.staff?) if @user + result = unseen_filter(result, @user.first_seen_at, @user.whisperer?) if @user result = remove_muted(result, @user, options) result = apply_shared_drafts(result, get_category_id(options[:category]), options) @@ -489,7 +489,7 @@ class TopicQuery def unread_results(options = {}) result = TopicQuery.unread_filter( default_results(options.reverse_merge(unordered: true)), - staff: @user&.staff?) + whisperer: @user&.whisperer?) .order('CASE WHEN topics.user_id = tu.user_id THEN 1 ELSE 2 END') if @user @@ -948,7 +948,7 @@ class TopicQuery def unread_messages(params) query = TopicQuery.unread_filter( messages_for_groups_or_user(params[:my_group_ids]), - staff: @user.staff? + whisperer: @user.whisperer? ) first_unread_pm_at = @@ -1084,10 +1084,10 @@ class TopicQuery private - def unseen_filter(list, user_first_seen_at, staff) + def unseen_filter(list, user_first_seen_at, whisperer) list = list.where("topics.bumped_at >= ?", user_first_seen_at) - col_name = staff ? "highest_staff_post_number" : "highest_post_number" + col_name = whisperer ? "highest_staff_post_number" : "highest_post_number" list.where("tu.last_read_post_number IS NULL OR tu.last_read_post_number < topics.#{col_name}") end end diff --git a/lib/topic_query/private_message_lists.rb b/lib/topic_query/private_message_lists.rb index 592c1d701f4..84b413fdcf1 100644 --- a/lib/topic_query/private_message_lists.rb +++ b/lib/topic_query/private_message_lists.rb @@ -165,7 +165,7 @@ class TopicQuery def filter_private_messages_unread(user, type) list = TopicQuery.unread_filter( private_messages_for(user, type), - staff: user.staff? + whisperer: user.whisperer? ) first_unread_pm_at = diff --git a/lib/topics_bulk_action.rb b/lib/topics_bulk_action.rb index 8326d5c7999..48f7819041b 100644 --- a/lib/topics_bulk_action.rb +++ b/lib/topics_bulk_action.rb @@ -69,7 +69,7 @@ class TopicsBulkAction end def dismiss_posts - highest_number_source_column = @user.staff? ? 'highest_staff_post_number' : 'highest_post_number' + highest_number_source_column = @user.whisperer? ? 'highest_staff_post_number' : 'highest_post_number' sql = <<~SQL UPDATE topic_users tu SET last_read_post_number = t.#{highest_number_source_column} diff --git a/lib/unread.rb b/lib/unread.rb index 27c8d046e2f..e59c388caa4 100644 --- a/lib/unread.rb +++ b/lib/unread.rb @@ -14,7 +14,7 @@ class Unread return 0 if @topic_user.last_read_post_number.blank? return 0 if do_not_notify?(@topic_user.notification_level) - highest_post_number = @guardian.is_staff? ? @topic.highest_staff_post_number : @topic.highest_post_number + highest_post_number = @guardian.is_whisperer? ? @topic.highest_staff_post_number : @topic.highest_post_number return 0 if @topic_user.last_read_post_number > highest_post_number diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js index 68c85e74581..1e31ecea228 100644 --- a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js +++ b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js @@ -42,7 +42,7 @@ export default Component.extend({ @discourseComputed("model.topic.id", "isReply", "isWhisper") whisperChannelName(topicId, isReply, isWhisper) { - if (topicId && this.currentUser.staff && (isReply || isWhisper)) { + if (topicId && this.currentUser.whisperer && (isReply || isWhisper)) { return `/discourse-presence/whisper/${topicId}`; } }, diff --git a/plugins/discourse-presence/test/javascripts/acceptance/discourse-presence-test.js b/plugins/discourse-presence/test/javascripts/acceptance/discourse-presence-test.js index e8ef9a4f008..d1bef67a0d1 100644 --- a/plugins/discourse-presence/test/javascripts/acceptance/discourse-presence-test.js +++ b/plugins/discourse-presence/test/javascripts/acceptance/discourse-presence-test.js @@ -15,7 +15,7 @@ import User from "discourse/models/user"; import selectKit from "discourse/tests/helpers/select-kit-helper"; acceptance("Discourse Presence Plugin", function (needs) { - needs.user(); + needs.user({ whisperer: true }); needs.settings({ enable_whispers: true }); test("Doesn't break topic creation", async function (assert) { diff --git a/spec/lib/bookmark_query_spec.rb b/spec/lib/bookmark_query_spec.rb index 2855013d290..cac0fd205dc 100644 --- a/spec/lib/bookmark_query_spec.rb +++ b/spec/lib/bookmark_query_spec.rb @@ -119,7 +119,10 @@ RSpec.describe BookmarkQuery do context "for a whispered post" do before do post_bookmark.bookmarkable.update(post_type: Post.types[:whisper]) + SiteSetting.enable_whispers = true end + fab!(:whisperers_group) { Fabricate(:group) } + context "when the user is moderator" do it "does return the whispered post" do user.update!(moderator: true) @@ -132,6 +135,13 @@ RSpec.describe BookmarkQuery do expect(bookmark_query.list_all.count).to eq(3) end end + context "when the user is a member of whisperers group" do + it "returns the whispered post" do + SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}" + user.update!(groups: [whisperers_group]) + expect(bookmark_query.list_all.count).to eq(3) + end + end context "when the user is not staff" do it "does not return the whispered post" do expect(bookmark_query.list_all.count).to eq(2) diff --git a/spec/lib/guardian_spec.rb b/spec/lib/guardian_spec.rb index 9c10598a7be..2e712115551 100644 --- a/spec/lib/guardian_spec.rb +++ b/spec/lib/guardian_spec.rb @@ -893,6 +893,8 @@ describe Guardian do end it 'respects whispers' do + SiteSetting.enable_whispers = true + SiteSetting.whispers_allowed_groups = "#{group.id}" regular_post = post whisper_post = Fabricate.build(:post, post_type: Post.types[:whisper]) @@ -916,6 +918,10 @@ describe Guardian do admin_guardian = Guardian.new(Fabricate.build(:admin)) expect(admin_guardian.can_see?(regular_post)).to eq(true) expect(admin_guardian.can_see?(whisper_post)).to eq(true) + + whisperer_guardian = Guardian.new(Fabricate(:user, groups: [group])) + expect(whisperer_guardian.can_see?(regular_post)).to eq(true) + expect(whisperer_guardian.can_see?(whisper_post)).to eq(true) end end diff --git a/spec/lib/post_destroyer_spec.rb b/spec/lib/post_destroyer_spec.rb index 5a5f7f573e6..b412ea58d8f 100644 --- a/spec/lib/post_destroyer_spec.rb +++ b/spec/lib/post_destroyer_spec.rb @@ -656,7 +656,7 @@ describe PostDestroyer do it 'should not set Topic#last_post_user_id to a whisperer' do post_1 = create_post(topic: post.topic, user: moderator) - whisper_1 = create_post(topic: post.topic, user: Fabricate(:user), post_type: Post.types[:whisper]) + create_post(topic: post.topic, user: Fabricate(:user), post_type: Post.types[:whisper]) whisper_2 = create_post(topic: post.topic, user: Fabricate(:user), post_type: Post.types[:whisper]) PostDestroyer.new(admin, whisper_2).destroy diff --git a/spec/lib/search_spec.rb b/spec/lib/search_spec.rb index b7a2e1af5f6..b7ece4605a4 100644 --- a/spec/lib/search_spec.rb +++ b/spec/lib/search_spec.rb @@ -910,7 +910,12 @@ describe Search do ]) end - it 'allows staff to search for whispers' do + it 'allows staff and members of whisperers group to search for whispers' do + whisperers_group = Fabricate(:group) + user = Fabricate(:user) + SiteSetting.enable_whispers = true + SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}" + post.update!(post_type: Post.types[:whisper], raw: 'this is a tiger') results = Search.execute('tiger') @@ -920,6 +925,13 @@ describe Search do results = Search.execute('tiger', guardian: Guardian.new(admin)) expect(results.posts).to eq([post]) + + results = Search.execute('tiger', guardian: Guardian.new(user)) + expect(results.posts).to eq([]) + + user.groups << whisperers_group + results = Search.execute('tiger', guardian: Guardian.new(user)) + expect(results.posts).to eq([post]) end end diff --git a/spec/lib/topic_query_spec.rb b/spec/lib/topic_query_spec.rb index fdb69252097..e1c93d0972d 100644 --- a/spec/lib/topic_query_spec.rb +++ b/spec/lib/topic_query_spec.rb @@ -834,6 +834,9 @@ describe TopicQuery do end context 'with whispers' do + before do + SiteSetting.enable_whispers = true + end it 'correctly shows up in unread for staff' do diff --git a/spec/lib/topic_view_spec.rb b/spec/lib/topic_view_spec.rb index b8151a4015d..e30746fb36e 100644 --- a/spec/lib/topic_view_spec.rb +++ b/spec/lib/topic_view_spec.rb @@ -343,6 +343,7 @@ RSpec.describe TopicView do context '.post_counts_by_user' do it 'returns the two posters with their appropriate counts' do + SiteSetting.enable_whispers = true Fabricate(:post, topic: topic, user: evil_trout, post_type: Post.types[:whisper]) # Should not be counted Fabricate(:post, topic: topic, user: evil_trout, post_type: Post.types[:whisper], action_code: 'assign') @@ -480,6 +481,7 @@ RSpec.describe TopicView do context 'whispers' do it "handles their visibility properly" do + SiteSetting.enable_whispers = true p1 = Fabricate(:post, topic: topic, user: evil_trout) p2 = Fabricate(:post, topic: topic, user: evil_trout, post_type: Post.types[:whisper]) p3 = Fabricate(:post, topic: topic, user: evil_trout) diff --git a/spec/lib/topics_bulk_action_spec.rb b/spec/lib/topics_bulk_action_spec.rb index 523473f44e0..1edcc92163a 100644 --- a/spec/lib/topics_bulk_action_spec.rb +++ b/spec/lib/topics_bulk_action_spec.rb @@ -95,6 +95,7 @@ describe TopicsBulkAction do context "when the highest_staff_post_number is > highest_post_number for a topic (e.g. whisper is last post)" do it "dismisses posts" do + SiteSetting.enable_whispers = true post1 = create_post(user: user) p = create_post(topic_id: post1.topic_id) create_post(topic_id: post1.topic_id) diff --git a/spec/lib/unread_spec.rb b/spec/lib/unread_spec.rb index 00d98b228e2..d559dd84804 100644 --- a/spec/lib/unread_spec.rb +++ b/spec/lib/unread_spec.rb @@ -4,8 +4,9 @@ require 'unread' describe Unread do - let (:user) { Fabricate.build(:user, id: 1) } - let (:topic) do + let(:whisperers_group) { Fabricate(:group) } + let(:user) { Fabricate(:user, id: 1, groups: [whisperers_group]) } + let(:topic) do Fabricate.build(:topic, posts_count: 13, highest_staff_post_number: 15, @@ -26,6 +27,7 @@ describe Unread do describe 'staff counts' do it 'should correctly return based on staff post number' do + SiteSetting.enable_whispers = true user.admin = true topic_user.last_read_post_number = 13 @@ -46,11 +48,20 @@ describe Unread do end it 'returns the right unread posts for a staff user' do + SiteSetting.enable_whispers = true + SiteSetting.whispers_allowed_groups = "" user.admin = true topic_user.last_read_post_number = 10 expect(unread.unread_posts).to eq(5) end + it 'returns the right unread posts for a whisperer user' do + SiteSetting.enable_whispers = true + SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}" + topic_user.last_read_post_number = 10 + expect(unread.unread_posts).to eq(5) + end + it 'should have 0 unread posts if the user has read more posts than exist (deleted)' do topic_user.last_read_post_number = 14 expect(unread.unread_posts).to eq(0) diff --git a/spec/models/private_message_topic_tracking_state_spec.rb b/spec/models/private_message_topic_tracking_state_spec.rb index 98b9ebae1e5..c2ade4da7c2 100644 --- a/spec/models/private_message_topic_tracking_state_spec.rb +++ b/spec/models/private_message_topic_tracking_state_spec.rb @@ -65,6 +65,7 @@ describe PrivateMessageTopicTrackingState do end it 'returns the right tracking state when topics contain whispers' do + SiteSetting.enable_whispers = true TopicUser.find_by(user: user_2, topic: private_message).update!( last_read_post_number: 1 ) diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index a8171cc84ad..910c9e8e86c 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -5,7 +5,8 @@ describe Topic do let(:now) { Time.zone.local(2013, 11, 20, 8, 0) } fab!(:user) { Fabricate(:user) } fab!(:user1) { Fabricate(:user) } - fab!(:user2) { Fabricate(:user) } + fab!(:whisperers_group) { Fabricate(:group) } + fab!(:user2) { Fabricate(:user, groups: [whisperers_group]) } fab!(:moderator) { Fabricate(:moderator) } fab!(:coding_horror) { Fabricate(:coding_horror) } fab!(:evil_trout) { Fabricate(:evil_trout) } @@ -167,6 +168,11 @@ describe Topic do context '#visible_post_types' do let(:types) { Post.types } + before do + SiteSetting.enable_whispers = true + SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}" + end + it "returns the appropriate types for anonymous users" do post_types = Topic.visible_post_types @@ -186,7 +192,16 @@ describe Topic do end it "returns the appropriate types for staff users" do - post_types = Topic.visible_post_types(Fabricate.build(:moderator)) + post_types = Topic.visible_post_types(moderator) + + expect(post_types).to include(types[:regular]) + expect(post_types).to include(types[:moderator_action]) + expect(post_types).to include(types[:small_action]) + expect(post_types).to include(types[:whisper]) + end + + it "returns the appropriate types for whisperer users" do + post_types = Topic.visible_post_types(user2) expect(post_types).to include(types[:regular]) expect(post_types).to include(types[:moderator_action]) diff --git a/spec/models/topic_tracking_state_spec.rb b/spec/models/topic_tracking_state_spec.rb index 17f13a836a6..792ff6a3137 100644 --- a/spec/models/topic_tracking_state_spec.rb +++ b/spec/models/topic_tracking_state_spec.rb @@ -3,6 +3,7 @@ describe TopicTrackingState do fab!(:user) { Fabricate(:user) } + fab!(:whisperers_group) { Fabricate(:group) } let(:post) do create_post @@ -25,6 +26,21 @@ describe TopicTrackingState do expect(data["payload"]["archetype"]).to eq(Archetype.default) end + it "publishes whisper post to staff users and members of whisperers group" do + whisperers_group = Fabricate(:group) + Fabricate(:user, groups: [whisperers_group]) + Fabricate(:topic_user_watching, topic: topic, user: user) + SiteSetting.enable_whispers = true + SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}" + post.update!(post_type: Post.types[:whisper]) + + message = MessageBus.track_publish("/latest") do + TopicTrackingState.publish_latest(post.topic, true) + end.first + + expect(message.group_ids).to contain_exactly(whisperers_group.id, Group::AUTO_GROUPS[:staff]) + end + describe 'private message' do it 'should not publish any message' do messages = MessageBus.track_publish do @@ -54,6 +70,7 @@ describe TopicTrackingState do end it 'correctly publish read for staff' do + SiteSetting.enable_whispers = true create_post( raw: "this is a test post", topic: post.topic, @@ -118,7 +135,32 @@ describe TopicTrackingState do expect(message.user_ids).to contain_exactly(other_user.id) end + it "publishes whisper post to staff users and members of whisperers group" do + whisperers_group = Fabricate(:group) + Fabricate(:topic_user_watching, topic: topic, user: user) + SiteSetting.enable_whispers = true + SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}" + post.update!(post_type: Post.types[:whisper]) + + messages = MessageBus.track_publish("/unread") do + TopicTrackingState.publish_unread(post) + end + + expect(messages).to eq([]) + + user.groups << whisperers_group + other_user.grant_admin! + + message = MessageBus.track_publish("/unread") do + TopicTrackingState.publish_unread(post) + end.first + + expect(message.user_ids).to contain_exactly(user.id, other_user.id) + expect(message.group_ids).to eq(nil) + end + it "does not publish whisper post to non-staff users" do + SiteSetting.enable_whispers = true post.update!(post_type: Post.types[:whisper]) messages = MessageBus.track_publish("/unread") do @@ -632,6 +674,7 @@ describe TopicTrackingState do describe ".report" do it "correctly reports topics with staff posts" do + SiteSetting.enable_whispers = true create_post( raw: "this is a test post", topic: topic, diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 173eadff6e6..11596fa911e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2645,4 +2645,37 @@ RSpec.describe User do expect(result).to be(true) end end + + describe "#whisperer?" do + before do + SiteSetting.enable_whispers = true + end + + it 'returns true for an admin user' do + admin = Fabricate.create(:admin) + expect(admin.whisperer?).to eq(true) + end + + it 'returns false for an admin user when whispers are not enabled' do + SiteSetting.enable_whispers = false + + admin = Fabricate.create(:admin) + expect(admin.whisperer?).to eq(false) + end + + it 'returns true for user belonging to whisperers groups' do + group = Fabricate(:group) + whisperer = Fabricate(:user) + user = Fabricate(:user) + SiteSetting.whispers_allowed_groups = "#{group.id}" + + expect(whisperer.whisperer?).to eq(false) + expect(user.whisperer?).to eq(false) + + group.add(whisperer) + + expect(whisperer.whisperer?).to eq(true) + expect(user.whisperer?).to eq(false) + end + end end diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index fa8396c389a..1b8342538fd 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -3890,6 +3890,7 @@ describe UsersController do end it "includes all post types for staff members" do + SiteSetting.enable_whispers = true sign_in(admin) get "/u/#{admin.username}.json", params: { include_post_count_for: topic.id } diff --git a/spec/serializers/user_post_bookmark_serializer_spec.rb b/spec/serializers/user_post_bookmark_serializer_spec.rb index f0d55f868a0..7e2d7d009cc 100644 --- a/spec/serializers/user_post_bookmark_serializer_spec.rb +++ b/spec/serializers/user_post_bookmark_serializer_spec.rb @@ -1,12 +1,18 @@ # frozen_string_literal: true RSpec.describe UserPostBookmarkSerializer do + let(:whisperers_group) { Fabricate(:group) } let(:user) { Fabricate(:user) } let(:post) { Fabricate(:post, user: user, topic: topic) } let(:topic) { Fabricate(:topic) } let!(:bookmark) { Fabricate(:bookmark, name: 'Test', user: user, bookmarkable: post) } - it "uses the correct highest_post_number column based on whether the user is staff" do + before do + SiteSetting.enable_whispers = true + SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}" + end + + it "uses the correct highest_post_number column based on whether the user is whisperer" do Fabricate(:post, topic: topic) Fabricate(:post, topic: topic) Fabricate(:whisper, topic: topic) @@ -16,7 +22,7 @@ RSpec.describe UserPostBookmarkSerializer do expect(serializer.highest_post_number).to eq(3) - user.update!(admin: true) + user.groups << whisperers_group expect(serializer.highest_post_number).to eq(4) end