From bb3c05ba0e0516e043b7548692770e107a834beb Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Fri, 26 May 2023 03:26:38 +0300 Subject: [PATCH] DEV: Allow plugins to hook into user preferences update process on the server (#21737) This commit introduces a new `within_user_updater_transaction` event that's triggered inside the transaction that saves user updates in `UserUpdater`. Plugins can hook into the transaction using the event to include custom changes in the transaction. Callbacks for this event receive 2 arguments: 1. the user being saved 2. the changed attributes that are passed to `UserUpdater`. There's also new modifier in this commit called `users_controller_update_user_params` to allow plugins to allowlist custom params in the `UsersController` which eventually end up getting passed as attributes to the `UserUpdater` and the new `within_user_updater_transaction` event where they can be used to perform additional updates using the custom params. ----- New API is used in https://github.com/discourse/discourse-mailinglist-integration/pull/1. --- app/controllers/users_controller.rb | 8 +++++- app/services/user_updater.rb | 1 + spec/requests/users_controller_spec.rb | 26 +++++++++++++++++++ spec/services/user_updater_spec.rb | 36 ++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c857838c22e..815266b0dd4 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -2015,7 +2015,13 @@ class UsersController < ApplicationController end deprecate_modify_user_params_method - modify_user_params(result) + result = modify_user_params(result) + DiscoursePluginRegistry.apply_modifier( + :users_controller_update_user_params, + result, + current_user, + params, + ) end # Plugins can use this to modify user parameters diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb index 32481fb5933..8c740f4c0e7 100644 --- a/app/services/user_updater.rb +++ b/app/services/user_updater.rb @@ -237,6 +237,7 @@ class UserUpdater attributes.fetch(:name) { "" }, ) end + DiscourseEvent.trigger(:within_user_updater_transaction, user, attributes) rescue Addressable::URI::InvalidURIError => e # Prevent 500 for crazy url input return saved diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 6e4d17be872..56874e84f35 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -2973,6 +2973,32 @@ RSpec.describe UsersController do end end end + + context "when a plugin introduces a users_controller_update_user_params modifier" do + before { sign_in(user) } + + after { DiscoursePluginRegistry.clear_modifiers! } + + it "allows the plugin to modify the user params" do + block_called = false + + plugin = Plugin::Instance.new + plugin.register_modifier( + :users_controller_update_user_params, + ) do |result, current_user, params| + block_called = true + expect(current_user.id).to eq(user.id) + result[:location] = params[:plugin_location_alias] + result + end + + put "/u/#{user.username}.json", params: { location: "abc", plugin_location_alias: "xyz" } + + expect(response.status).to eq(200) + expect(user.reload.user_profile.location).to eq("xyz") + expect(block_called).to eq(true) + end + end end describe "#badge_title" do diff --git a/spec/services/user_updater_spec.rb b/spec/services/user_updater_spec.rb index abd6a44d24d..7573dd5f99e 100644 --- a/spec/services/user_updater_spec.rb +++ b/spec/services/user_updater_spec.rb @@ -46,6 +46,42 @@ RSpec.describe UserUpdater do expect(user.reload.name).to eq "Jim Tom" end + describe "the within_user_updater_transaction event" do + it "allows plugins to perform additional updates" do + update_attributes = { name: "Jimmmy Johnny" } + handler = + Proc.new do |user, attrs| + user.user_profile.update!(bio_raw: "hello world I'm Jimmmy") + expect(attrs).to eq(update_attributes) + end + DiscourseEvent.on(:within_user_updater_transaction, &handler) + + updater = UserUpdater.new(user, user) + updater.update(update_attributes) + + expect(user.reload.name).to eq("Jimmmy Johnny") + expect(user.user_profile.bio_raw).to eq("hello world I'm Jimmmy") + ensure + DiscourseEvent.off(:within_user_updater_transaction, &handler) + end + + it "can cancel the whole update transaction if a handler raises" do + error_class = Class.new(StandardError) + handler = Proc.new { raise error_class.new } + + DiscourseEvent.on(:within_user_updater_transaction, &handler) + + old_name = user.name + updater = UserUpdater.new(user, user) + + expect { updater.update(name: "Failure McClario") }.to raise_error(error_class) + + expect(user.reload.name).to eq(old_name) + ensure + DiscourseEvent.off(:within_user_updater_transaction, &handler) + end + end + it "can update categories and tags" do updater = UserUpdater.new(user, user) updater.update(watched_tags: "#{tag.name},#{tag2.name}", muted_category_ids: [category.id])