diff --git a/lib/discourse_plugin_registry.rb b/lib/discourse_plugin_registry.rb index 697d93474f4..53406bf0f96 100644 --- a/lib/discourse_plugin_registry.rb +++ b/lib/discourse_plugin_registry.rb @@ -116,6 +116,8 @@ class DiscoursePluginRegistry define_filtered_register :summarization_strategies + define_filtered_register :post_action_notify_user_handlers + def self.register_auth_provider(auth_provider) self.auth_providers << auth_provider end diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb index 7c775b5453a..ec11c01db46 100644 --- a/lib/guardian/post_guardian.rb +++ b/lib/guardian/post_guardian.rb @@ -58,7 +58,19 @@ module PostGuardian if action_key == :notify_user && !@user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map) - return false + # The modifier below is used to add additional permissions for notifying users. + # In core the only method of notifying a user is personal messages so we check if the + # user can PM. Plugins can extend the behavior of how users are notifier via `notify_user` + # post action, and this allows extension for that use case. + can_notify = false + can_notify = + DiscoursePluginRegistry.apply_modifier( + :post_guardian_can_notify_user, + can_notify, + self, + post, + ) + return can_notify end # we allow flagging for trust level 1 and higher diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index a3f3887b3ab..af82b6411a1 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -1238,6 +1238,14 @@ class Plugin::Instance DiscoursePluginRegistry.register_summarization_strategy(strategy, self) end + ## + # Register a block that will be called when PostActionCreator is going to notify a + # user of a post action. If any of these handlers returns false the default PostCreator + # call will be skipped. + def register_post_action_notify_user_handler(handler) + DiscoursePluginRegistry.register_post_action_notify_user_handler(handler, self) + end + protected def self.js_path diff --git a/lib/post_action_creator.rb b/lib/post_action_creator.rb index 409bb94eb0b..f1e46d855ca 100644 --- a/lib/post_action_creator.rb +++ b/lib/post_action_creator.rb @@ -116,12 +116,16 @@ class PostActionCreator # create meta topic / post if needed if @message.present? && %i[notify_moderators notify_user spam].include?(@post_action_name) creator = create_message_creator - post = creator.create - if creator.errors.present? - result.add_errors_from(creator) - return result + # We need to check if the creator exists because it's possible `create_message_creator` returns nil + # in the event that a `post_action_notify_user_handler` evaluated to false, haulting the post creation. + if creator + post = creator.create + if creator.errors.present? + result.add_errors_from(creator) + return result + end + @meta_post = post end - @meta_post = post end begin @@ -339,8 +343,16 @@ class PostActionCreator else create_args[:subtype] = TopicSubtype.notify_user - create_args[:target_usernames] = if @post_action_name == :notify_user - @post.user.username + if @post_action_name == :notify_user + create_args[:target_usernames] = @post.user.username + + # Evaluate DiscoursePluginRegistry.post_action_notify_user_handlers. + # If any return false, return early from this method + handler_values = + DiscoursePluginRegistry.post_action_notify_user_handlers.map do |handler| + handler.call(@created_by, @post, @message) + end + return if handler_values.any? { |value| value == false } elsif @post_action_name != :notify_moderators # this is a hack to allow a PM with no recipients, we should think through # a cleaner technique, a PM with myself is valid for flagging diff --git a/spec/lib/post_action_creator_spec.rb b/spec/lib/post_action_creator_spec.rb index 81aeba92dfd..fef228d75b3 100644 --- a/spec/lib/post_action_creator_spec.rb +++ b/spec/lib/post_action_creator_spec.rb @@ -327,4 +327,73 @@ RSpec.describe PostActionCreator do ) end end + + describe "With plugin adding post_action_notify_user_handlers" do + let(:message) { "oh that was really bad what you said there" } + let(:plugin) { Plugin::Instance.new } + + after { DiscoursePluginRegistry.reset! } + + it "evaluates all handlers and creates post if none return false" do + plugin.register_post_action_notify_user_handler( + Proc.new do |user, post, message| + MessageBus.publish("notify_user", { user_id: user.id, message: message }) + end, + ) + + plugin.register_post_action_notify_user_handler( + Proc.new do |user, post, message| + MessageBus.publish("notify_user", { poster_id: post.user_id, message: message }) + end, + ) + + messages = + MessageBus.track_publish("notify_user") do + result = + PostActionCreator.new( + user, + post, + PostActionType.types[:notify_user], + message: message, + flag_topic: false, + ).perform + post_action = result.post_action + expect(post_action.related_post).to be_present + end + + expect( + messages.find { |m| m.data[:user_id] == user.id && m.data[:message] == message }, + ).to be_present + expect( + messages.find { |m| m.data[:poster_id] == post.user_id && m.data[:message] == message }, + ).to be_present + end + + it "evaluates all handlers and doesn't create a post one returns false" do + plugin.register_post_action_notify_user_handler( + Proc.new do |user, post, message| + MessageBus.publish("notify_user", { user_id: user.id, message: message }) + false + end, + ) + + messages = + MessageBus.track_publish("notify_user") do + result = + PostActionCreator.new( + user, + post, + PostActionType.types[:notify_user], + message: message, + flag_topic: false, + ).perform + post_action = result.post_action + expect(post_action.related_post).not_to be_present + end + + expect( + messages.find { |m| m.data[:user_id] == user.id && m.data[:message] == message }, + ).to be_present + end + end end