diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d1cb02993eafb..99db0beddb0de 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -516,36 +516,40 @@ def category_badge(category, opts = nil) CategoryBadge.html_for(category, opts).html_safe end - SERVER_PLUGIN_OUTLET_PLUGINS_PREFIXES = [Rails.root.join("plugins/").to_s] - private_constant :SERVER_PLUGIN_OUTLET_PLUGINS_PREFIXES - - if Rails.env.test? - SERVER_PLUGIN_OUTLET_PLUGINS_PREFIXES << Rails.root.join("spec/fixtures/plugins/").to_s - end - - SERVER_PLUGIN_OUTLET_CONNECTOR_TEMPLATES = - SERVER_PLUGIN_OUTLET_PLUGINS_PREFIXES.each_with_object({}) do |plugins_prefix, connectors| - Dir - .glob("#{plugins_prefix}*/app/views/connectors/**/*.html.erb") - .each do |template_path| - template_path =~ Regexp.new("/connectors/(.*)/.*\.html\.erb$") - outlet_name = Regexp.last_match(1) - connectors[outlet_name] ||= [] - connectors[outlet_name] << template_path.sub(plugins_prefix, "").delete_suffix( - ".html.erb", - ) - end - end - private_constant :SERVER_PLUGIN_OUTLET_CONNECTOR_TEMPLATES + def self.all_connectors + @all_connectors = Dir.glob("plugins/*/app/views/connectors/**/*.html.erb") + end + + PLUGIN_OUTLET_TEMPLATE_CACHE = Concurrent::Map.new def server_plugin_outlet(name, locals: {}) return "" if !GlobalSetting.load_plugins? - return "" if !SERVER_PLUGIN_OUTLET_CONNECTOR_TEMPLATES.key?(name) - lookup_context.append_view_paths(SERVER_PLUGIN_OUTLET_PLUGINS_PREFIXES) + matcher = Regexp.new("/connectors/#{name}/.*\.html\.erb$") + erbs = ApplicationHelper.all_connectors.select { |c| c =~ matcher } + return "" if erbs.blank? + + erbs + .map do |erb| + cache_key = [erb, locals.keys.sort] + + template = + PLUGIN_OUTLET_TEMPLATE_CACHE.compute_if_absent(cache_key) do + source = File.read(erb) + handler = ActionView::Template.handler_for_extension("erb") + + ActionView::Template.new( + source, + "discourse_plugin_outlet__#{name}", + handler, + locals: locals.keys, + format: :html, + virtual_path: erb, + ) + end - SERVER_PLUGIN_OUTLET_CONNECTOR_TEMPLATES[name] - .map { |template| render template:, locals: } + render template: template, locals: locals + end .join .html_safe end diff --git a/config/initializers/development.rb b/config/initializers/development.rb new file mode 100644 index 0000000000000..6f87eee7d099a --- /dev/null +++ b/config/initializers/development.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +if Rails.env.development? + Rails.application.reloader.to_prepare { ApplicationHelper::PLUGIN_OUTLET_TEMPLATE_CACHE.clear } +end diff --git a/spec/fixtures/plugins/my_plugin/app/views/connectors/topic_header/template_1.html.erb b/spec/fixtures/plugins/my_plugin/app/views/connectors/topic_header/template_1.html.erb deleted file mode 100644 index a3a87d213c129..0000000000000 --- a/spec/fixtures/plugins/my_plugin/app/views/connectors/topic_header/template_1.html.erb +++ /dev/null @@ -1 +0,0 @@ -Fixture from my_plugin template 1: <%= @topic_view.topic.title %> diff --git a/spec/fixtures/plugins/my_plugin/app/views/connectors/topic_header/template_2.html.erb b/spec/fixtures/plugins/my_plugin/app/views/connectors/topic_header/template_2.html.erb deleted file mode 100644 index a764779135709..0000000000000 --- a/spec/fixtures/plugins/my_plugin/app/views/connectors/topic_header/template_2.html.erb +++ /dev/null @@ -1 +0,0 @@ -Fixture from my_plugin template 2: <%= @topic_view.topic.title %> diff --git a/spec/fixtures/plugins/my_plugin_2/app/views/connectors/topic_header/template_1.html.erb b/spec/fixtures/plugins/my_plugin_2/app/views/connectors/topic_header/template_1.html.erb deleted file mode 100644 index f3e5e96aecef9..0000000000000 --- a/spec/fixtures/plugins/my_plugin_2/app/views/connectors/topic_header/template_1.html.erb +++ /dev/null @@ -1 +0,0 @@ -Fixture from my_plugin_2 template 1: <%= @topic_view.topic.title %> diff --git a/spec/fixtures/plugins/my_plugin_2/app/views/connectors/topic_header/template_2.html.erb b/spec/fixtures/plugins/my_plugin_2/app/views/connectors/topic_header/template_2.html.erb deleted file mode 100644 index e5ae169e6283d..0000000000000 --- a/spec/fixtures/plugins/my_plugin_2/app/views/connectors/topic_header/template_2.html.erb +++ /dev/null @@ -1 +0,0 @@ -Fixture from my_plugin_2 template 2: <%= @topic_view.topic.title %> diff --git a/spec/fixtures/plugins/my_plugin_3/app/views/connectors/some_other_outlet/template_1.html.erb b/spec/fixtures/plugins/my_plugin_3/app/views/connectors/some_other_outlet/template_1.html.erb deleted file mode 100644 index 7b4c63cf889b3..0000000000000 --- a/spec/fixtures/plugins/my_plugin_3/app/views/connectors/some_other_outlet/template_1.html.erb +++ /dev/null @@ -1 +0,0 @@ -Fixture from my_plugin_3 template 1: <%= @topic_view.topic.title %> diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb index 3b840f580cd7c..f1667a1a1a693 100644 --- a/spec/requests/topics_controller_spec.rb +++ b/spec/requests/topics_controller_spec.rb @@ -40,35 +40,34 @@ describe "topic_header plugin outlet" do fab!(:another_topic) { Fabricate(:topic, title: "Another topic by me") } - before { global_setting(:load_plugins?, true) } + let(:tmp_dir) { Dir.mktmpdir } + let(:template_dir) do + path = File.join(tmp_dir, "connectors", "topic_header") + FileUtils.mkdir_p(path) + path + end + let(:template_file) do + file = Tempfile.new(%w[test_template .html.erb], template_dir) + file.write("Topic title from outlet: <%= @topic_view.topic.title %>") + file.close + file + end - it "renders the connector templates from multiple plugins" do - get "/t/#{topic.slug}/#{topic.id}" + before do + global_setting(:load_plugins?, true) + ApplicationHelper.stubs(:all_connectors).returns([template_file.path]) + end + + after { FileUtils.remove_entry(tmp_dir) if tmp_dir } + it "doesn't leak state between requests" do + get "/t/#{topic.slug}/#{topic.id}" expect(response.status).to eq(200) - expect(response.body).to include("Fixture from my_plugin template 1: #{topic.title}") - expect(response.body).to include("Fixture from my_plugin template 2: #{topic.title}") - expect(response.body).to include("Fixture from my_plugin_2 template 1: #{topic.title}") - expect(response.body).to include("Fixture from my_plugin_2 template 2: #{topic.title}") - expect(response.body).not_to include("Fixture from my_plugin_3 template 1: #{topic.title}") + expect(response.body).to include("Topic title from outlet: #{topic.title}") get "/t/#{another_topic.slug}/#{another_topic.id}" - expect(response.status).to eq(200) - expect(response.body).to include("Fixture from my_plugin template 1: #{another_topic.title}") - expect(response.body).to include("Fixture from my_plugin template 2: #{another_topic.title}") - - expect(response.body).to include( - "Fixture from my_plugin_2 template 1: #{another_topic.title}", - ) - - expect(response.body).to include( - "Fixture from my_plugin_2 template 2: #{another_topic.title}", - ) - - expect(response.body).not_to include( - "Fixture from my_plugin_3 template 1: #{another_topic.title}", - ) + expect(response.body).to include("Topic title from outlet: #{another_topic.title}") end end