Helpers for plugins to support enabling/disabling

This commit is contained in:
Robin Ward 2015-02-04 16:23:39 -05:00
parent 530b20d339
commit 25daca8f23
6 changed files with 135 additions and 14 deletions

View File

@ -39,18 +39,35 @@
Nobody says hello :'(
{{/plugin-outlet}}
```
## Disabling
If a plugin returns a disabled status, the outlets will not be wired up for it.
The list of disabled plugins is returned via the `Site` singleton.
**/
var _connectorCache;
function findOutlets(collection, callback) {
Ember.keys(collection).forEach(function(i) {
if (i.indexOf("/connectors/") !== -1) {
var segments = i.split("/"),
var disabledPlugins = Discourse.Site.currentProp('disabled_plugins') || [];
Ember.keys(collection).forEach(function(res) {
if (res.indexOf("/connectors/") !== -1) {
// Skip any disabled plugins
for (var i=0; i<disabledPlugins.length; i++) {
if (res.indexOf("/" + disabledPlugins[i] + "/") !== -1) {
return;
}
}
var segments = res.split("/"),
outletName = segments[segments.length-2],
uniqueName = segments[segments.length-1];
callback(outletName, i, uniqueName);
callback(outletName, res, uniqueName);
}
});
}
@ -59,18 +76,18 @@ function buildConnectorCache() {
_connectorCache = {};
var uniqueViews = {};
findOutlets(requirejs._eak_seen, function(outletName, idx, uniqueName) {
findOutlets(requirejs._eak_seen, function(outletName, resource, uniqueName) {
_connectorCache[outletName] = _connectorCache[outletName] || [];
var viewClass = require(idx, null, null, true).default;
var viewClass = require(resource, null, null, true).default;
uniqueViews[uniqueName] = viewClass;
_connectorCache[outletName].pushObject(viewClass);
});
findOutlets(Ember.TEMPLATES, function(outletName, idx, uniqueName) {
findOutlets(Ember.TEMPLATES, function(outletName, resource, uniqueName) {
_connectorCache[outletName] = _connectorCache[outletName] || [];
var mixin = {templateName: idx.replace('javascripts/', '')},
var mixin = {templateName: resource.replace('javascripts/', '')},
viewClass = uniqueViews[uniqueName];
if (viewClass) {
@ -81,7 +98,6 @@ function buildConnectorCache() {
}
_connectorCache[outletName].pushObject(viewClass.extend(mixin));
});
}
export default function(connectionName, options) {

View File

@ -130,6 +130,16 @@ class ApplicationController < ActionController::Base
end
end
class PluginDisabled < Exception; end
# If a controller requires a plugin, it will raise an exception if that plugin is
# disabled. This allows plugins to be disabled programatically.
def self.requires_plugin(plugin_name)
before_filter do
raise PluginDisabled.new if Discourse.disabled_plugin_names.include?(plugin_name)
end
end
def set_current_user_for_logs
if current_user
Logster.add_to_env(request.env,"username",current_user.username)

View File

@ -9,7 +9,8 @@ class SiteSerializer < ApplicationSerializer
:top_menu_items,
:anonymous_top_menu_items,
:uncategorized_category_id, # this is hidden so putting it here
:is_readonly
:is_readonly,
:disabled_plugins
has_many :categories, serializer: BasicCategorySerializer, embed: :objects
has_many :post_action_types, embed: :objects
@ -51,4 +52,8 @@ class SiteSerializer < ApplicationSerializer
Discourse.readonly_mode?
end
def disabled_plugins
Discourse.disabled_plugin_names
end
end

View File

@ -84,6 +84,11 @@ module Discourse
@plugins.each { |plugin| plugin.activate! }
end
def self.disabled_plugin_names
return [] if @plugins.blank?
@plugins.select {|p| !p.enabled?}.map(&:name)
end
def self.plugins
@plugins
end

View File

@ -39,15 +39,35 @@ class Plugin::Instance
end
end
def name
metadata.name
def enabled?
return @enabled_site_setting ? SiteSetting.send(@enabled_site_setting) : true
end
delegate :name, to: :metadata
def add_to_serializer(serializer, attr, &block)
klass = "#{serializer.to_s.classify}Serializer".constantize
klass.attributes(attr)
klass.send(:define_method, attr, &block)
# Don't include serialized methods if the plugin is disabled
plugin = self
klass.send(:define_method, "include_#{attr}?") do
plugin.enabled?
end
end
# Extend a class but check that the plugin is enabled
def add_to_class(klass, attr, &block)
klass = klass.to_s.classify.constantize
hidden_method_name = "#{attr}_without_enable_check".to_sym
klass.send(:define_method, hidden_method_name, &block)
plugin = self
klass.send(:define_method, attr) do |*args|
send(hidden_method_name, *args) if plugin.enabled?
end
end
# will make sure all the assets this plugin needs are registered
@ -95,13 +115,20 @@ class Plugin::Instance
initializers << block
end
# A proxy to `DiscourseEvent.on` which does nothing if the plugin is disabled
def on(event_name, &block)
DiscourseEvent.on(event_name) do |*args|
block.call(*args) if enabled?
end
end
def notify_after_initialize
color_schemes.each do |c|
ColorScheme.create_from_base(name: c[:name], colors: c[:colors]) unless ColorScheme.where(name: c[:name]).exists?
end
initializers.each do |callback|
callback.call
callback.call(self)
end
end
@ -235,6 +262,10 @@ class Plugin::Instance
end
end
def enabled_site_setting(setting)
@enabled_site_setting = setting
end
protected
def register_assets!

View File

@ -23,6 +23,60 @@ describe Plugin::Instance do
end
end
context "enabling/disabling" do
it "is enabled by default" do
expect(Plugin::Instance.new.enabled?).to eq(true)
end
context "with a plugin that extends things" do
class Trout; end
class TroutSerializer < ApplicationSerializer; end
class TroutPlugin < Plugin::Instance
attr_accessor :enabled
def enabled?; @enabled; end
end
before do
@plugin = TroutPlugin.new
@trout = Trout.new
# New method
@plugin.add_to_class(:trout, :status?) { "evil" }
# DiscourseEvent
@hello_count = 0
@plugin.on(:hello) { @hello_count += 1 }
# Serializer
@plugin.add_to_serializer(:trout, :scales) { 1024 }
@serializer = TroutSerializer.new(@trout)
end
it "checks enabled/disabled functionality for extensions" do
# with an enabled plugin
@plugin.enabled = true
expect(@trout.status?).to eq("evil")
DiscourseEvent.trigger(:hello)
expect(@hello_count).to eq(1)
expect(@serializer.scales).to eq(1024)
expect(@serializer.include_scales?).to eq(true)
# When a plugin is disabled
@plugin.enabled = false
expect(@trout.status?).to eq(nil)
DiscourseEvent.trigger(:hello)
expect(@hello_count).to eq(1)
expect(@serializer.scales).to eq(1024)
expect(@serializer.include_scales?).to eq(false)
end
end
end
context "register asset" do
it "populates the DiscoursePluginRegistry" do
plugin = Plugin::Instance.new nil, "/tmp/test.rb"