2014-05-03 05:46:03 +08:00
|
|
|
require_dependency 'sass/discourse_sass_compiler'
|
2014-04-01 19:42:14 +08:00
|
|
|
|
2013-02-06 03:16:51 +08:00
|
|
|
class SiteCustomization < ActiveRecord::Base
|
2013-02-07 15:11:56 +08:00
|
|
|
ENABLED_KEY = '7e202ef2-56d7-47d5-98d8-a9c8d15e57dd'
|
2013-02-19 08:02:00 +08:00
|
|
|
# placing this in uploads to ease deployment rules
|
|
|
|
CACHE_PATH = 'uploads/stylesheet-cache'
|
2013-02-07 23:45:24 +08:00
|
|
|
@lock = Mutex.new
|
2013-02-06 03:16:51 +08:00
|
|
|
|
2013-02-07 23:45:24 +08:00
|
|
|
before_create do
|
2013-02-06 03:16:51 +08:00
|
|
|
self.position ||= (SiteCustomization.maximum(:position) || 0) + 1
|
|
|
|
self.enabled ||= false
|
|
|
|
self.key ||= SecureRandom.uuid
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2014-03-08 21:25:03 +08:00
|
|
|
def compile_stylesheet(scss)
|
2014-05-03 05:46:03 +08:00
|
|
|
DiscourseSassCompiler.compile(scss, 'custom')
|
2014-04-07 09:34:35 +08:00
|
|
|
rescue => e
|
|
|
|
puts e.backtrace.join("\n") unless Sass::SyntaxError === e
|
|
|
|
|
|
|
|
raise e
|
2014-03-08 21:25:03 +08:00
|
|
|
end
|
|
|
|
|
2013-02-07 23:45:24 +08:00
|
|
|
before_save do
|
2013-09-17 00:21:49 +08:00
|
|
|
['stylesheet', 'mobile_stylesheet'].each do |stylesheet_attr|
|
|
|
|
if self.send("#{stylesheet_attr}_changed?")
|
|
|
|
begin
|
2014-03-08 21:25:03 +08:00
|
|
|
self.send("#{stylesheet_attr}_baked=", compile_stylesheet(self.send(stylesheet_attr)))
|
2013-09-17 00:21:49 +08:00
|
|
|
rescue Sass::SyntaxError => e
|
2014-05-03 05:46:03 +08:00
|
|
|
self.send("#{stylesheet_attr}_baked=", DiscourseSassCompiler.error_as_css(e, "custom stylesheet"))
|
2013-09-17 00:21:49 +08:00
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-04-02 13:24:36 +08:00
|
|
|
after_save do
|
2013-03-01 02:54:12 +08:00
|
|
|
if stylesheet_changed?
|
2013-09-17 00:21:49 +08:00
|
|
|
File.delete(stylesheet_fullpath) if File.exists?(stylesheet_fullpath)
|
|
|
|
end
|
|
|
|
if mobile_stylesheet_changed?
|
|
|
|
File.delete(stylesheet_fullpath(:mobile)) if File.exists?(stylesheet_fullpath(:mobile))
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
2013-03-01 02:54:12 +08:00
|
|
|
remove_from_cache!
|
2013-09-17 00:21:49 +08:00
|
|
|
if stylesheet_changed? or mobile_stylesheet_changed?
|
|
|
|
ensure_stylesheets_on_disk!
|
|
|
|
# TODO: this is broken now because there's mobile stuff too
|
2013-03-01 02:54:12 +08:00
|
|
|
MessageBus.publish "/file-change/#{key}", stylesheet_hash
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
2013-03-01 02:54:12 +08:00
|
|
|
MessageBus.publish "/header-change/#{key}", header if header_changed?
|
2014-04-02 13:24:36 +08:00
|
|
|
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
2014-04-02 13:24:36 +08:00
|
|
|
after_destroy do
|
2013-03-01 02:54:12 +08:00
|
|
|
if File.exists?(stylesheet_fullpath)
|
|
|
|
File.delete stylesheet_fullpath
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
2013-09-17 00:21:49 +08:00
|
|
|
if File.exists?(stylesheet_fullpath(:mobile))
|
|
|
|
File.delete stylesheet_fullpath(:mobile)
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
self.remove_from_cache!
|
|
|
|
end
|
|
|
|
|
2013-02-07 21:55:04 +08:00
|
|
|
def self.enabled_key
|
|
|
|
ENABLED_KEY.dup << RailsMultisite::ConnectionManagement.current_db
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.enabled_style_key
|
2013-02-07 15:11:56 +08:00
|
|
|
@cache ||= {}
|
2013-03-01 02:54:12 +08:00
|
|
|
preview_style = @cache[enabled_key]
|
|
|
|
return if preview_style == :none
|
2013-02-07 15:11:56 +08:00
|
|
|
return preview_style if preview_style
|
|
|
|
|
2013-02-07 23:45:24 +08:00
|
|
|
@lock.synchronize do
|
2014-05-06 21:41:59 +08:00
|
|
|
style = find_by(enabled: true)
|
2013-02-07 15:11:56 +08:00
|
|
|
if style
|
2013-03-01 02:54:12 +08:00
|
|
|
@cache[enabled_key] = style.key
|
2013-02-07 15:11:56 +08:00
|
|
|
else
|
2013-03-01 02:54:12 +08:00
|
|
|
@cache[enabled_key] = :none
|
|
|
|
nil
|
2013-02-07 15:11:56 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
|
2013-09-17 00:21:49 +08:00
|
|
|
def self.custom_stylesheet(preview_style, target=:desktop)
|
2013-02-07 21:55:04 +08:00
|
|
|
preview_style ||= enabled_style_key
|
2013-02-06 03:16:51 +08:00
|
|
|
style = lookup_style(preview_style)
|
2013-09-17 00:21:49 +08:00
|
|
|
style.stylesheet_link_tag(target).html_safe if style
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
2013-09-17 00:21:49 +08:00
|
|
|
def self.custom_header(preview_style, target=:desktop)
|
2013-02-07 21:55:04 +08:00
|
|
|
preview_style ||= enabled_style_key
|
2013-02-06 03:16:51 +08:00
|
|
|
style = lookup_style(preview_style)
|
2013-09-17 00:55:44 +08:00
|
|
|
if style && ((target != :mobile && style.header) || (target == :mobile && style.mobile_header))
|
2013-09-17 00:21:49 +08:00
|
|
|
target == :mobile ? style.mobile_header.html_safe : style.header.html_safe
|
2013-02-07 15:25:18 +08:00
|
|
|
else
|
|
|
|
""
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.override_default_style(preview_style)
|
2013-02-07 21:55:04 +08:00
|
|
|
preview_style ||= enabled_style_key
|
2013-02-06 03:16:51 +08:00
|
|
|
style = lookup_style(preview_style)
|
|
|
|
style.override_default_style if style
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.lookup_style(key)
|
|
|
|
return if key.blank?
|
2013-02-07 23:45:24 +08:00
|
|
|
|
|
|
|
# cache is cross site resiliant cause key is secure random
|
2013-02-06 03:16:51 +08:00
|
|
|
@cache ||= {}
|
|
|
|
ensure_cache_listener
|
|
|
|
style = @cache[key]
|
|
|
|
return style if style
|
2013-02-07 23:45:24 +08:00
|
|
|
|
|
|
|
@lock.synchronize do
|
2014-05-06 21:41:59 +08:00
|
|
|
style = find_by(key: key)
|
2013-09-17 00:21:49 +08:00
|
|
|
style.ensure_stylesheets_on_disk! if style
|
2013-02-06 03:16:51 +08:00
|
|
|
@cache[key] = style
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.ensure_cache_listener
|
|
|
|
unless @subscribed
|
|
|
|
klass = self
|
|
|
|
MessageBus.subscribe("/site_customization") do |msg|
|
|
|
|
message = msg.data
|
2013-02-07 23:45:24 +08:00
|
|
|
klass.remove_from_cache!(message["key"], false)
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
@subscribed = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-03-01 02:54:12 +08:00
|
|
|
def self.remove_from_cache!(key, broadcast = true)
|
|
|
|
MessageBus.publish('/site_customization', key: key) if broadcast
|
2013-02-06 03:16:51 +08:00
|
|
|
if @cache
|
2013-02-07 23:45:24 +08:00
|
|
|
@lock.synchronize do
|
2013-02-06 03:16:51 +08:00
|
|
|
@cache[key] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def remove_from_cache!
|
2013-02-07 21:55:04 +08:00
|
|
|
self.class.remove_from_cache!(self.class.enabled_key)
|
2013-03-01 02:54:12 +08:00
|
|
|
self.class.remove_from_cache!(key)
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
2013-09-17 00:21:49 +08:00
|
|
|
def stylesheet_hash(target=:desktop)
|
|
|
|
Digest::MD5.hexdigest( target == :mobile ? mobile_stylesheet : stylesheet )
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
2013-02-10 20:05:11 +08:00
|
|
|
def cache_fullpath
|
|
|
|
"#{Rails.root}/public/#{CACHE_PATH}"
|
|
|
|
end
|
|
|
|
|
2013-09-17 00:21:49 +08:00
|
|
|
def ensure_stylesheets_on_disk!
|
|
|
|
[[:desktop, 'stylesheet_baked'], [:mobile, 'mobile_stylesheet_baked']].each do |target, baked_attr|
|
|
|
|
path = stylesheet_fullpath(target)
|
|
|
|
dir = cache_fullpath
|
2014-03-07 16:22:40 +08:00
|
|
|
FileUtils.mkdir_p(dir)
|
2013-09-17 00:21:49 +08:00
|
|
|
unless File.exists?(path)
|
|
|
|
File.open(path, "w") do |f|
|
|
|
|
f.puts self.send(baked_attr)
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-09-17 00:21:49 +08:00
|
|
|
def stylesheet_filename(target=:desktop)
|
|
|
|
target == :desktop ? "/#{self.key}.css" : "/#{target}_#{self.key}.css"
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
2013-09-17 00:21:49 +08:00
|
|
|
def stylesheet_fullpath(target=:desktop)
|
|
|
|
"#{cache_fullpath}#{stylesheet_filename(target)}"
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
2013-09-17 00:21:49 +08:00
|
|
|
def stylesheet_link_tag(target=:desktop)
|
|
|
|
return mobile_stylesheet_link_tag if target == :mobile
|
2013-03-01 02:54:12 +08:00
|
|
|
return "" unless stylesheet.present?
|
2013-02-06 03:16:51 +08:00
|
|
|
return @stylesheet_link_tag if @stylesheet_link_tag
|
2013-09-17 00:21:49 +08:00
|
|
|
ensure_stylesheets_on_disk!
|
2014-07-29 12:55:48 +08:00
|
|
|
@stylesheet_link_tag = link_css_tag "/#{CACHE_PATH}#{stylesheet_filename}?#{stylesheet_hash}"
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
2013-09-17 00:21:49 +08:00
|
|
|
|
|
|
|
def mobile_stylesheet_link_tag
|
|
|
|
return "" unless mobile_stylesheet.present?
|
|
|
|
return @mobile_stylesheet_link_tag if @mobile_stylesheet_link_tag
|
|
|
|
ensure_stylesheets_on_disk!
|
2014-07-29 12:55:48 +08:00
|
|
|
@mobile_stylesheet_link_tag = link_css_tag "/#{CACHE_PATH}#{stylesheet_filename(:mobile)}?#{stylesheet_hash(:mobile)}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def link_css_tag(href)
|
|
|
|
href = (GlobalSetting.cdn_url || "") + href
|
|
|
|
%Q{<link class="custom-css" rel="stylesheet" href="#{href}" type="text/css" media="all">}
|
2013-09-17 00:21:49 +08:00
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
2013-05-24 10:48:32 +08:00
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: site_customizations
|
|
|
|
#
|
2013-10-04 11:28:49 +08:00
|
|
|
# id :integer not null, primary key
|
|
|
|
# name :string(255) not null
|
|
|
|
# stylesheet :text
|
|
|
|
# header :text
|
|
|
|
# position :integer not null
|
|
|
|
# user_id :integer not null
|
|
|
|
# enabled :boolean not null
|
|
|
|
# key :string(255) not null
|
2014-05-28 09:49:50 +08:00
|
|
|
# created_at :datetime
|
|
|
|
# updated_at :datetime
|
2013-10-04 11:28:49 +08:00
|
|
|
# override_default_style :boolean default(FALSE), not null
|
|
|
|
# stylesheet_baked :text default(""), not null
|
|
|
|
# mobile_stylesheet :text
|
|
|
|
# mobile_header :text
|
|
|
|
# mobile_stylesheet_baked :text
|
2013-05-24 10:48:32 +08:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
|
|
|
# index_site_customizations_on_key (key)
|
|
|
|
#
|