2013-08-23 16:21:52 +10:00
require 'digest/sha1'
require 'fileutils'
require_dependency 'plugin/metadata'
require_dependency 'plugin/auth_provider'
class Plugin :: Instance
2013-08-26 11:04:16 +10:00
attr_accessor :path , :metadata
2015-02-06 17:32:59 -05:00
attr_reader :admin_route
2013-08-23 16:21:52 +10:00
2015-02-04 12:59:18 -05:00
# Memoized array readers
[ :assets , :auth_providers , :color_schemes , :initializers , :javascripts , :styles ] . each do | att |
class_eval %Q{
def #{att}
@ #{att} ||= []
end
}
end
2015-06-04 15:56:17 -04:00
def seed_data
@seed_data || = { }
end
2013-08-23 16:21:52 +10:00
def self . find_all ( parent_path )
[ ] . tap { | plugins |
2013-10-21 11:18:24 +02:00
# also follows symlinks - http://stackoverflow.com/q/357754
2014-11-03 15:26:06 -05:00
Dir [ " #{ parent_path } /**/*/**/plugin.rb " ] . sort . each do | path |
2013-08-23 16:21:52 +10:00
source = File . read ( path )
metadata = Plugin :: Metadata . parse ( source )
plugins << self . new ( metadata , path )
end
}
end
2013-08-26 11:04:16 +10:00
def initialize ( metadata = nil , path = nil )
2013-08-23 16:21:52 +10:00
@metadata = metadata
@path = path
end
2015-02-06 17:32:59 -05:00
def add_admin_route ( label , location )
@admin_route = { label : label , location : location }
end
2015-02-04 16:23:39 -05:00
def enabled?
2015-04-23 19:33:29 +02:00
@enabled_site_setting ? SiteSetting . send ( @enabled_site_setting ) : true
2013-08-26 11:04:16 +10:00
end
2015-02-04 16:23:39 -05:00
delegate :name , to : :metadata
2015-04-23 19:33:29 +02:00
def add_to_serializer ( serializer , attr , define_include_method = true , & block )
2015-01-12 10:52:55 -05:00
klass = " #{ serializer . to_s . classify } Serializer " . constantize
2015-04-23 19:33:29 +02:00
klass . attributes ( attr ) unless attr . to_s . start_with? ( " include_ " )
2015-01-12 10:52:55 -05:00
klass . send ( :define_method , attr , & block )
2015-02-04 16:23:39 -05:00
2015-04-23 19:33:29 +02:00
return unless define_include_method
2015-02-04 16:23:39 -05:00
# Don't include serialized methods if the plugin is disabled
plugin = self
2015-04-23 19:33:29 +02:00
klass . send ( :define_method , " include_ #{ attr } ? " ) { plugin . enabled? }
2015-02-04 16:23:39 -05:00
end
# Extend a class but check that the plugin is enabled
def add_to_class ( klass , attr , & block )
klass = klass . to_s . classify . constantize
2015-04-23 19:33:29 +02:00
hidden_method_name = :" #{ attr } _without_enable_check "
2015-04-23 20:02:16 +02:00
klass . send ( :define_method , hidden_method_name , & block )
2015-02-04 16:23:39 -05:00
plugin = self
klass . send ( :define_method , attr ) do | * args |
send ( hidden_method_name , * args ) if plugin . enabled?
end
2015-01-12 10:52:55 -05:00
end
2015-04-23 19:33:29 +02:00
# Add validation method but check that the plugin is enabled
2015-04-26 00:12:19 +02:00
def validate ( klass , name , & block )
2015-04-23 19:33:29 +02:00
klass = klass . to_s . classify . constantize
2015-04-26 00:12:19 +02:00
klass . send ( :define_method , name , & block )
2015-04-23 19:33:29 +02:00
plugin = self
2015-04-26 00:12:19 +02:00
klass . validate ( name , if : - > { plugin . enabled? } )
2015-04-23 19:33:29 +02:00
end
2013-08-23 16:21:52 +10:00
# will make sure all the assets this plugin needs are registered
def generate_automatic_assets!
paths = [ ]
automatic_assets . each do | path , contents |
unless File . exists? path
ensure_directory path
File . open ( path , " w " ) do | f |
f . write ( contents )
end
end
paths << path
end
delete_extra_automatic_assets ( paths )
paths
end
def delete_extra_automatic_assets ( good_paths )
2013-09-20 14:39:14 -07:00
return unless Dir . exists? auto_generated_path
2013-08-23 16:21:52 +10:00
filenames = good_paths . map { | f | File . basename ( f ) }
# nuke old files
Dir . foreach ( auto_generated_path ) do | p |
next if [ " . " , " .. " ] . include? ( p )
next if filenames . include? ( p )
File . delete ( auto_generated_path + " / #{ p } " )
end
end
def ensure_directory ( path )
dirname = File . dirname ( path )
unless File . directory? ( dirname )
FileUtils . mkdir_p ( dirname )
end
end
def auto_generated_path
File . dirname ( path ) << " /auto_generated "
end
2013-09-17 10:23:21 +10:00
def after_initialize ( & block )
2015-02-04 12:59:18 -05:00
initializers << block
2013-09-17 10:23:21 +10:00
end
2015-02-04 16:23:39 -05:00
# 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
2013-09-17 10:23:21 +10:00
def notify_after_initialize
2014-06-03 12:36:34 -04:00
color_schemes . each do | c |
ColorScheme . create_from_base ( name : c [ :name ] , colors : c [ :colors ] ) unless ColorScheme . where ( name : c [ :name ] ) . exists?
end
2015-02-04 12:59:18 -05:00
initializers . each do | callback |
2015-02-04 16:23:39 -05:00
callback . call ( self )
2013-09-17 10:23:21 +10:00
end
end
2014-12-11 17:08:47 +01:00
def listen_for ( event_name )
return unless self . respond_to? ( event_name )
DiscourseEvent . on ( event_name , & self . method ( event_name ) )
end
2013-08-23 16:21:52 +10:00
def register_css ( style )
2015-02-04 12:59:18 -05:00
styles << style
2013-08-23 16:21:52 +10:00
end
def register_javascript ( js )
2015-02-04 12:59:18 -05:00
javascripts << js
2013-08-23 16:21:52 +10:00
end
2014-06-05 11:39:33 +10:00
def register_custom_html ( hash )
DiscoursePluginRegistry . custom_html || = { }
DiscoursePluginRegistry . custom_html . merge! ( hash )
end
2014-04-07 16:33:35 +02:00
def register_asset ( file , opts = nil )
2013-08-23 16:21:52 +10:00
full_path = File . dirname ( path ) << " /assets/ " << file
2014-04-10 16:30:22 +10:00
assets << [ full_path , opts ]
2013-08-23 16:21:52 +10:00
end
2014-06-03 12:36:34 -04:00
def register_color_scheme ( name , colors )
color_schemes << { name : name , colors : colors }
2015-06-04 15:56:17 -04:00
end
def register_seed_data ( key , value )
seed_data [ key ] = value
end
2014-06-03 12:36:34 -04:00
2013-08-23 16:21:52 +10:00
def automatic_assets
2015-02-04 12:59:18 -05:00
css = styles . join ( " \n " )
js = javascripts . join ( " \n " )
2013-08-23 16:21:52 +10:00
2015-02-04 12:59:18 -05:00
auth_providers . each do | auth |
overrides = " "
overrides = " , titleOverride: ' #{ auth . title } ' " if auth . title
overrides << " , messageOverride: ' #{ auth . message } ' " if auth . message
overrides << " , frameWidth: ' #{ auth . frame_width } ' " if auth . frame_width
overrides << " , frameHeight: ' #{ auth . frame_height } ' " if auth . frame_height
2013-08-23 16:21:52 +10:00
2015-02-04 12:59:18 -05:00
js << " Discourse.LoginMethod.register(Discourse.LoginMethod.create({name: ' #{ auth . name } ' #{ overrides } })); \n "
2013-08-23 16:21:52 +10:00
2015-02-04 12:59:18 -05:00
if auth . glyph
css << " .btn-social. #{ auth . name } :before{ content: ' #{ auth . glyph } '; } \n "
end
2013-08-23 16:21:52 +10:00
2015-02-04 12:59:18 -05:00
if auth . background_color
css << " .btn-social. #{ auth . name } { background: #{ auth . background_color } ; } \n "
2013-08-23 16:21:52 +10:00
end
end
2013-09-19 17:59:17 -07:00
# Generate an IIFE for the JS
js = " (function(){ #{ js } })(); " if js . present?
2013-08-23 16:21:52 +10:00
2013-09-19 17:59:17 -07:00
result = [ ]
result << [ css , 'css' ] if css . present?
result << [ js , 'js' ] if js . present?
result . map do | asset , extension |
2013-08-23 16:21:52 +10:00
hash = Digest :: SHA1 . hexdigest asset
[ " #{ auto_generated_path } /plugin_ #{ hash } . #{ extension } " , asset ]
end
end
2014-04-10 16:30:22 +10:00
2013-08-23 16:21:52 +10:00
# note, we need to be able to parse seperately to activation.
# this allows us to present information about a plugin in the UI
# prior to activations
def activate!
2015-04-27 13:06:53 -04:00
if @path
# Automatically include all ES6 JS and hbs files
root_path = " #{ File . dirname ( @path ) } /assets/javascripts "
DiscoursePluginRegistry . register_glob ( root_path , 'js.es6' )
DiscoursePluginRegistry . register_glob ( root_path , 'hbs' )
end
2013-09-12 11:27:13 +10:00
self . instance_eval File . read ( path ) , path
2013-08-23 16:21:52 +10:00
if auto_assets = generate_automatic_assets!
2014-04-10 16:30:22 +10:00
assets . concat auto_assets . map { | a | [ a ] }
2013-08-23 16:21:52 +10:00
end
2014-12-30 16:29:28 -05:00
register_assets! unless assets . blank?
2015-06-04 15:56:17 -04:00
seed_data . each do | key , value |
DiscoursePluginRegistry . register_seed_data ( key , value )
end
2015-05-04 16:01:57 +02:00
# TODO: possibly amend this to a rails engine
# Automatically include assets
2014-12-30 16:29:28 -05:00
Rails . configuration . assets . paths << auto_generated_path
Rails . configuration . assets . paths << File . dirname ( path ) + " /assets "
2013-08-23 16:21:52 +10:00
2015-05-04 16:01:57 +02:00
# Automatically include rake tasks
Rake . add_rakelib ( File . dirname ( path ) + " /lib/tasks " )
# Automatically include migrations
Rails . configuration . paths [ " db/migrate " ] << File . dirname ( path ) + " /db/migrate "
2013-11-20 14:38:21 +11:00
public_data = File . dirname ( path ) + " /public "
if Dir . exists? ( public_data )
target = Rails . root . to_s + " /public/plugins/ "
` mkdir -p #{ target } `
2014-01-17 15:35:52 -08:00
target << name . gsub ( / \ s / , " _ " )
2013-11-20 14:38:21 +11:00
# TODO a cleaner way of registering and unregistering
2013-11-20 17:31:58 +11:00
` rm -f #{ target } `
` ln -s #{ public_data } #{ target } `
2013-11-20 14:38:21 +11:00
end
2013-08-23 16:21:52 +10:00
end
2014-04-10 16:30:22 +10:00
2013-08-26 12:52:36 +10:00
def auth_provider ( opts )
2013-08-23 16:21:52 +10:00
provider = Plugin :: AuthProvider . new
2013-08-26 12:52:36 +10:00
[ :glyph , :background_color , :title , :message , :frame_width , :frame_height , :authenticator ] . each do | sym |
2013-08-23 16:21:52 +10:00
provider . send " #{ sym } = " , opts . delete ( sym )
end
2015-02-04 12:59:18 -05:00
auth_providers << provider
2013-08-23 16:21:52 +10:00
end
# shotgun approach to gem loading, in future we need to hack bundler
# to at least determine dependencies do not clash before loading
#
# Additionally we want to support multiple ruby versions correctly and so on
#
# This is a very rough initial implementation
def gem ( name , version , opts = { } )
gems_path = File . dirname ( path ) + " /gems/ #{ RUBY_VERSION } "
spec_path = gems_path + " /specifications "
spec_file = spec_path + " / #{ name } - #{ version } .gemspec "
unless File . exists? spec_file
2014-01-17 12:27:09 +11:00
command = " gem install #{ name } -v #{ version } -i #{ gems_path } --no-document --ignore-dependencies "
2013-12-31 09:57:04 +11:00
if opts [ :source ]
command << " --source #{ opts [ :source ] } "
end
2013-08-23 16:21:52 +10:00
puts command
2013-08-26 13:52:15 +10:00
puts ` #{ command } `
2013-08-23 16:21:52 +10:00
end
if File . exists? spec_file
spec = Gem :: Specification . load spec_file
spec . activate
unless opts [ :require ] == false
2014-02-11 15:53:54 -08:00
require opts [ :require_name ] ? opts [ :require_name ] : name
2013-08-23 16:21:52 +10:00
end
else
puts " You are specifying the gem #{ name } in #{ path } , however it does not exist! "
2013-11-20 14:38:21 +11:00
exit ( - 1 )
2013-08-23 16:21:52 +10:00
end
end
2015-07-02 09:45:17 -07:00
def enabled_site_setting ( setting = nil )
if setting
@enabled_site_setting = setting
else
@enabled_site_setting
end
2015-02-04 16:23:39 -05:00
end
2014-04-10 16:30:22 +10:00
protected
def register_assets!
assets . each do | asset , opts |
2014-12-09 14:20:53 -05:00
DiscoursePluginRegistry . register_asset ( asset , opts )
2014-04-10 16:30:22 +10:00
end
end
2013-08-23 16:21:52 +10:00
end