discourse/app/controllers/composer_controller.rb
Bianca Nenciu 93859037ef
FEATURE: Improve composer warnings for mentions (#18796)
* FEATURE: Show warning if group cannot be mentioned

A similar warning is displayed when the user cannot be mentioned because
they have not been invited to the topic.

* FEATURE: Resolve mentions for new topic

This commit improves several improvements and refactors
/u/is_local_username route to a better /composer/mentions route that
can handle new topics too.

* FEATURE: Show warning if only some are notified

Sometimes users are still notified even if the group that was mentioned
was not invited to the message. This happens because its members were
invited directly or are members of other groups that were invited.

* DEV: Refactor _warnCannotSeeMention
2022-12-05 20:22:05 +02:00

185 lines
5.4 KiB
Ruby

# frozen_string_literal: true
class ComposerController < ApplicationController
requires_login
def mentions
@names = params.require(:names)
raise Discourse::InvalidParameters.new(:names) if !@names.kind_of?(Array) || @names.size > 20
if params[:topic_id].present?
@topic = Topic.find_by(id: params[:topic_id])
guardian.ensure_can_see!(@topic)
end
# allowed_names is necessary just for new private messages.
@allowed_names = if params[:allowed_names].present?
raise Discourse::InvalidParameters(:allowed_names) if !params[:allowed_names].is_a?(Array)
params[:allowed_names] << current_user.username
else
[]
end
user_reasons = {}
group_reasons = {}
@names.each do |name|
if user = users[name]
reason = user_reason(user)
user_reasons[name] = reason if reason.present?
elsif group = groups[name]
reason = group_reason(group)
group_reasons[name] = reason if reason.present?
end
end
if @topic && @names.include?(SiteSetting.here_mention) && guardian.can_mention_here?
here_count = PostAlerter.new.expand_here_mention(@topic.first_post, exclude_ids: [current_user.id]).size
end
serialized_groups = groups.values.reduce({}) do |hash, group|
serialized_group = { user_count: group.user_count }
if group_reasons[group.name] == :not_allowed &&
members_visible_group_ids.include?(group.id) &&
(@topic&.private_message? || @allowed_names.present?)
# Find users that are notified already because they have been invited
# directly or via a group
notified_count = GroupUser
# invited directly
.where(user_id: topic_allowed_user_ids)
.or(
# invited via a group
GroupUser.where(
user_id: GroupUser.where(group_id: topic_allowed_group_ids).select(:user_id)
)
)
.where(group_id: group.id)
.select(:user_id).distinct.count
if notified_count > 0
group_reasons[group.name] = :some_not_allowed
serialized_group[:notified_count] = notified_count
end
end
hash[group.name] = serialized_group
hash
end
render json: {
users: users.keys,
user_reasons: user_reasons,
groups: serialized_groups,
group_reasons: group_reasons,
here_count: here_count,
max_users_notified_per_group_mention: SiteSetting.max_users_notified_per_group_mention,
}
end
private
def user_reason(user)
reason = if @topic && !user.guardian.can_see?(@topic)
@topic.private_message? ? :private : :category
elsif @allowed_names.present? && !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids)
# This would normally be handled by the previous if, but that does not work for new private messages.
:private
elsif topic_muted_by.include?(user.id)
:muted_topic
elsif @topic&.private_message? && !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids)
# Admins can see the topic, but they will not be mentioned if they were not invited.
:not_allowed
end
# Regular users can see only basic information why the users cannot see the topic.
reason = nil if !guardian.is_staff? && reason != :private && reason != :category
reason
end
def group_reason(group)
if !mentionable_group_ids.include?(group.id)
:not_mentionable
elsif (@topic&.private_message? || @allowed_names.present?) && !topic_allowed_group_ids.include?(group.id)
:not_allowed
end
end
def is_user_allowed?(user, user_ids, group_ids)
user_ids.include?(user.id) || user.group_ids.any? { |group_id| group_ids.include?(group_id) }
end
def users
@users ||= User
.not_staged
.where(username_lower: @names.map(&:downcase))
.index_by(&:username_lower)
end
def groups
@groups ||= Group
.visible_groups(current_user)
.where('lower(name) IN (?)', @names.map(&:downcase))
.index_by(&:name)
end
def mentionable_group_ids
@mentionable_group_ids ||= Group
.mentionable(current_user, include_public: false)
.where(name: @names)
.pluck(:id)
.to_set
end
def members_visible_group_ids
@members_visible_group_ids ||= Group
.members_visible_groups(current_user)
.where(name: @names)
.pluck(:id)
.to_set
end
def topic_muted_by
@topic_muted_by ||= if @topic.present?
TopicUser
.where(topic: @topic)
.where(user_id: users.values.map(&:id))
.where(notification_level: TopicUser.notification_levels[:muted])
.pluck(:user_id)
.to_set
else
Set.new
end
end
def topic_allowed_user_ids
@topic_allowed_user_ids ||= if @allowed_names.present?
User
.where(username_lower: @allowed_names.map(&:downcase))
.pluck(:id)
.to_set
elsif @topic&.private_message?
TopicAllowedUser
.where(topic: @topic)
.pluck(:user_id)
.to_set
end
end
def topic_allowed_group_ids
@topic_allowed_group_ids ||= if @allowed_names.present?
Group
.messageable(current_user)
.where(name: @allowed_names)
.pluck(:id)
.to_set
elsif @topic&.private_message?
TopicAllowedGroup
.where(topic: @topic)
.pluck(:group_id)
.to_set
end
end
end