require_dependency 'sass/discourse_sass_compiler' require_dependency 'sass/discourse_stylesheets' require_dependency 'distributed_cache' class SiteCustomization < ActiveRecord::Base ENABLED_KEY = '7e202ef2-56d7-47d5-98d8-a9c8d15e57dd' @cache = DistributedCache.new('site_customization') before_create do self.enabled ||= false self.key ||= SecureRandom.uuid true end def compile_stylesheet(scss) DiscourseSassCompiler.compile(scss, 'custom') rescue => e puts e.backtrace.join("\n") unless Sass::SyntaxError === e raise e end before_save do ['stylesheet', 'mobile_stylesheet'].each do |stylesheet_attr| if self.send("#{stylesheet_attr}_changed?") begin self.send("#{stylesheet_attr}_baked=", compile_stylesheet(self.send(stylesheet_attr))) rescue Sass::SyntaxError => e self.send("#{stylesheet_attr}_baked=", DiscourseSassCompiler.error_as_css(e, "custom stylesheet")) end end end end after_save do remove_from_cache! if stylesheet_changed? || mobile_stylesheet_changed? MessageBus.publish "/file-change/#{key}", SecureRandom.hex MessageBus.publish "/file-change/#{SiteCustomization::ENABLED_KEY}", SecureRandom.hex end MessageBus.publish "/header-change/#{key}", header if header_changed? MessageBus.publish "/footer-change/#{key}", footer if footer_changed? DiscourseStylesheets.cache.clear end after_destroy do remove_from_cache! end def self.enabled_key ENABLED_KEY.dup << RailsMultisite::ConnectionManagement.current_db end def self.enabled_stylesheet_contents(target=:desktop) @cache["enabled_stylesheet_#{target}"] ||= where(enabled: true) .order(:name) .pluck(target == :desktop ? :stylesheet_baked : :mobile_stylesheet_baked) .compact .join("\n") end def self.stylesheet_contents(key, target) if key == ENABLED_KEY enabled_stylesheet_contents(target) else where(key: key) .pluck(target == :mobile ? :mobile_stylesheet_baked : :stylesheet_baked) .first end end def self.custom_stylesheet(preview_style=nil, target=:desktop) preview_style ||= ENABLED_KEY if preview_style == ENABLED_KEY stylesheet_link_tag(ENABLED_KEY, target, enabled_stylesheet_contents(target)) else lookup_field(preview_style, target, :stylesheet_link_tag) end end %i{header top footer head_tag body_tag}.each do |name| define_singleton_method("custom_#{name}") do |preview_style=nil, target=:desktop| preview_style ||= ENABLED_KEY lookup_field(preview_style, target, name) end end def self.lookup_field(key, target, field) return if key.blank? cache_key = key + target.to_s + field.to_s; lookup = @cache[cache_key] return lookup.html_safe if lookup styles = if key == ENABLED_KEY order(:name).where(enabled:true).to_a else [find_by(key: key)].compact end val = if styles.present? styles.map do |style| lookup = target == :mobile ? "mobile_#{field}" : field style.send(lookup) end.compact.join("\n") end (@cache[cache_key] = val || "").html_safe end def self.remove_from_cache!(key, broadcast = true) MessageBus.publish('/site_customization', key: key) if broadcast clear_cache! end def self.clear_cache! @cache.clear end def remove_from_cache! self.class.remove_from_cache!(self.class.enabled_key) self.class.remove_from_cache!(key) end def mobile_stylesheet_link_tag stylesheet_link_tag(:mobile) end def stylesheet_link_tag(target=:desktop) content = target == :mobile ? mobile_stylesheet : stylesheet SiteCustomization.stylesheet_link_tag(key, target, content) end def self.stylesheet_link_tag(key, target, content) return "" unless content.present? hash = Digest::MD5.hexdigest(content) link_css_tag "/site_customizations/#{key}.css?target=#{target}&v=#{hash}" end def self.link_css_tag(href) href = (GlobalSetting.cdn_url || "") + "#{GlobalSetting.relative_url_root}#{href}&__ws=#{Discourse.current_hostname}" %Q{}.html_safe end end # == Schema Information # # Table name: site_customizations # # id :integer not null, primary key # name :string(255) not null # stylesheet :text # header :text # user_id :integer not null # enabled :boolean not null # key :string(255) not null # created_at :datetime not null # updated_at :datetime not null # stylesheet_baked :text default(""), not null # mobile_stylesheet :text # mobile_header :text # mobile_stylesheet_baked :text # footer :text # mobile_footer :text # head_tag :text # body_tag :text # top :text # mobile_top :text # # Indexes # # index_site_customizations_on_key (key) #