discourse/lib/stylesheet/importer.rb
David Taylor 7878e5007a
FIX: Refactor to prevent themes affecting core stylesheets (#7029)
If a theme setting contained invalid SCSS, it would cause an error 500 on the site, with no way to recover. This commit stops loading theme settings in the core stylesheets, and instead only loads the color scheme variables. This change also makes `common/foundation/variables.scss` available to themes without an explicit import.
2019-02-19 15:55:59 +00:00

165 lines
4.3 KiB
Ruby

require_dependency 'stylesheet/common'
require_dependency 'global_path'
module Stylesheet
class Importer < SassC::Importer
include GlobalPath
def self.special_imports
@special_imports ||= {}
end
def self.register_import(name, &blk)
special_imports[name] = blk
end
register_import "theme_field" do
Import.new("theme_field.scss", source: @theme_field)
end
register_import "plugins" do
import_files(DiscoursePluginRegistry.stylesheets)
end
register_import "plugins_mobile" do
import_files(DiscoursePluginRegistry.mobile_stylesheets)
end
register_import "plugins_desktop" do
import_files(DiscoursePluginRegistry.desktop_stylesheets)
end
register_import "plugins_variables" do
import_files(DiscoursePluginRegistry.sass_variables)
end
register_import "theme_colors" do
contents = ""
colors = (@theme_id && theme.color_scheme) ? theme.color_scheme.resolved_colors : ColorScheme.base_colors
colors.each do |n, hex|
contents << "$#{n}: ##{hex} !default;\n"
end
contents
Import.new("theme_colors.scss", source: contents)
end
register_import "theme_variables" do
contents = ""
theme&.all_theme_variables&.each do |field|
if field.type_id == ThemeField.types[:theme_upload_var]
if upload = field.upload
url = upload_cdn_path(upload.url)
contents << "$#{field.name}: unquote(\"#{url}\");\n"
end
else
contents << to_scss_variable(field.name, field.value)
end
end
theme&.included_settings&.each do |name, value|
contents << to_scss_variable(name, value)
end
Import.new("theme_variable.scss", source: contents)
end
register_import "category_backgrounds" do
contents = ""
Category.where('uploaded_background_id IS NOT NULL').each do |c|
contents << category_css(c) if c.uploaded_background&.url.present?
end
Import.new("category_background.scss", source: contents)
end
register_import "embedded_theme" do
next unless @theme_id
theme_import(:common, :embedded_scss)
end
register_import "mobile_theme" do
next unless @theme_id
theme_import(:mobile, :scss)
end
register_import "desktop_theme" do
next unless @theme_id
theme_import(:desktop, :scss)
end
def initialize(options)
@theme = options[:theme]
@theme_id = options[:theme_id]
@theme_field = options[:theme_field]
if @theme && !@theme_id
# make up an id so other stuff does not bail out
@theme_id = @theme.id || -1
end
end
def import_files(files)
files.map do |file|
# we never want inline css imports, they are a mess
# this tricks libsass so it imports inline instead
if file =~ /\.css$/
file = file[0..-5]
end
Import.new(file)
end
end
def theme_import(target, attr)
fields = theme.list_baked_fields(target, attr)
fields.map do |field|
value = field.value
if value.present?
filename = "#{field.theme.id}/#{field.target_name}-#{field.name}-#{field.theme.name.parameterize}.scss"
with_comment = <<COMMENT
// Theme: #{field.theme.name}
// Target: #{field.target_name} #{field.name}
// Last Edited: #{field.updated_at}
#{value}
COMMENT
Import.new(filename, source: with_comment)
end
end.compact
end
def theme
unless @theme
@theme = (@theme_id && Theme.find(@theme_id)) || :nil
end
@theme == :nil ? nil : @theme
end
def category_css(category)
"body.category-#{category.full_slug} { background-image: url(#{upload_cdn_path(category.uploaded_background.url)}) }\n"
end
def to_scss_variable(name, value)
escaped = value.to_s.gsub('"', "\\22")
escaped.gsub!("\n", "\\A")
"$#{name}: unquote(\"#{escaped}\");\n"
end
def imports(asset, parent_path)
if asset[-1] == "*"
Dir["#{Stylesheet::ASSET_ROOT}/#{asset}.scss"].map do |path|
Import.new(asset[0..-2] + File.basename(path, ".*"))
end
elsif callback = Importer.special_imports[asset]
instance_eval(&callback)
else
Import.new(asset + ".scss")
end
end
end
end