mirror of
https://github.com/discourse/discourse.git
synced 2026-08-09 21:23:14 +08:00
Silenced users can now no longer like posts or use reactions, which closes a potential griefing vector that was difficult for moderators to monitor. The implementation adds a silenced check to the guardian's post_can_act? method for likes, and introduces a new can_use_reactions? guardian method in the discourse-reactions plugin that delegates to the same logic. This ensures both features share the same authorization path. Additionally, silenced users' custom status is now shadow-banned: visible to themselves and staff, but hidden from other users. A new `can_see_user_status?` guardian method centralizes the visibility logic, used by serializers and MessageBus publishing. Status updates from silenced users are now only broadcast to themselves and staff. Also includes minor CSS fixes for user status spacing and alignment. Chat reactions already had proper silenced user checks in place via the can_react? guardian method, so no changes were needed there. Ref - t/140084
147 lines
4.1 KiB
Ruby
Vendored
147 lines
4.1 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module DiscourseReactions
|
|
class ReactionManager
|
|
attr_reader :reaction_value, :previous_reaction_value
|
|
|
|
def initialize(reaction_value:, user:, post:)
|
|
@reaction_value = reaction_value
|
|
@user = user
|
|
@post = post
|
|
@like =
|
|
@post.post_actions.find_by(
|
|
user: @user,
|
|
post_action_type_id: PostActionType::LIKE_POST_ACTION_ID,
|
|
)
|
|
@previous_reaction_value =
|
|
if @like && !reaction_user
|
|
DiscourseReactions::Reaction.main_reaction_id
|
|
elsif reaction_user
|
|
old_reaction_value(reaction_user)
|
|
end
|
|
end
|
|
|
|
def toggle!
|
|
raise Discourse::InvalidAccess unless @user.guardian.can_use_reactions?(@post)
|
|
|
|
if (@like && !@user.guardian.can_delete_post_action?(@like)) ||
|
|
(reaction_user && !@user.guardian.can_delete_reaction_user?(reaction_user))
|
|
raise Discourse::InvalidAccess
|
|
end
|
|
|
|
ActiveRecord::Base.transaction do
|
|
@reaction = reaction_scope&.first_or_create
|
|
@reaction_user = reaction_user_scope
|
|
if @reaction_value == DiscourseReactions::Reaction.main_reaction_id
|
|
toggle_like
|
|
else
|
|
toggle_reaction
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def toggle_like
|
|
if reaction_user.present?
|
|
remove_reaction
|
|
@reaction = reaction_scope&.first_or_create
|
|
add_reaction
|
|
else
|
|
@like ? remove_shadow_like : add_shadow_like
|
|
end
|
|
end
|
|
|
|
def toggle_reaction
|
|
if reaction_user.present?
|
|
remove_reaction
|
|
return if previous_reaction_value && previous_reaction_value == @reaction_value
|
|
end
|
|
|
|
remove_shadow_like if @like
|
|
add_reaction if reaction_user.blank?
|
|
end
|
|
|
|
def add_reaction_notification
|
|
DiscourseReactions::ReactionNotification.new(@reaction, @user).create
|
|
end
|
|
|
|
def remove_reaction_notification
|
|
DiscourseReactions::ReactionNotification.new(@reaction, @user).delete
|
|
end
|
|
|
|
def reaction_scope
|
|
DiscourseReactions::Reaction.where(
|
|
post_id: @post.id,
|
|
reaction_value: @reaction_value,
|
|
reaction_type: DiscourseReactions::Reaction.reaction_types["emoji"],
|
|
)
|
|
end
|
|
|
|
def reaction_user_scope
|
|
return nil unless @reaction
|
|
search_reaction_user =
|
|
DiscourseReactions::ReactionUser.where(user_id: @user.id, post_id: @post.id)
|
|
create_reaction_user =
|
|
DiscourseReactions::ReactionUser.new(
|
|
reaction_id: @reaction.id,
|
|
user_id: @user.id,
|
|
post_id: @post.id,
|
|
)
|
|
if search_reaction_user.length > 0
|
|
search_reaction_user.first
|
|
else
|
|
create_reaction_user
|
|
end
|
|
end
|
|
|
|
def reaction_user
|
|
DiscourseReactions::ReactionUser.find_by(user_id: @user.id, post_id: @post.id)
|
|
end
|
|
|
|
def old_reaction_value(reaction_user)
|
|
return unless reaction_user
|
|
DiscourseReactions::Reaction.where(id: reaction_user.reaction_id).first&.reaction_value
|
|
end
|
|
|
|
def add_shadow_like(notify: true)
|
|
silent = true
|
|
PostActionCreator.like(@user, @post, silent)
|
|
add_reaction_notification if notify
|
|
end
|
|
|
|
def remove_shadow_like
|
|
PostActionDestroyer.new(@user, @post, PostActionType::LIKE_POST_ACTION_ID).perform
|
|
delete_like_reaction
|
|
remove_reaction_notification
|
|
end
|
|
|
|
def delete_like_reaction
|
|
DiscourseReactions::Reaction.where(
|
|
reaction_value: DiscourseReactions::Reaction.main_reaction_id,
|
|
post_id: @post.id,
|
|
).destroy_all
|
|
end
|
|
|
|
def add_reaction
|
|
@reaction_user = reaction_user_scope if reaction_user.blank?
|
|
@reaction_user.save!
|
|
add_shadow_like(notify: false) if !reaction_excluded_from_like?
|
|
add_reaction_notification
|
|
end
|
|
|
|
def remove_reaction
|
|
@reaction_user.destroy
|
|
remove_shadow_like
|
|
delete_reaction_with_no_users
|
|
end
|
|
|
|
def delete_reaction_with_no_users
|
|
DiscourseReactions::Reaction.where(reaction_users_count: 0, post_id: @post.id).destroy_all
|
|
end
|
|
|
|
def reaction_excluded_from_like?
|
|
DiscourseReactions::Reaction.reactions_excluded_from_like.include?(@reaction_value)
|
|
end
|
|
end
|
|
end
|