2017-05-11 06:16:57 +08:00
|
|
|
require_dependency 'upload_creator'
|
2019-01-23 22:40:21 +08:00
|
|
|
require_dependency 'theme_store/tgz_exporter'
|
2017-11-14 13:30:23 +08:00
|
|
|
require 'base64'
|
2017-05-11 06:16:57 +08:00
|
|
|
|
2017-04-12 22:52:52 +08:00
|
|
|
class Admin::ThemesController < Admin::AdminController
|
|
|
|
|
2019-01-23 22:40:21 +08:00
|
|
|
skip_before_action :check_xhr, only: [:show, :preview, :export]
|
2017-04-15 01:35:12 +08:00
|
|
|
|
|
|
|
def preview
|
|
|
|
@theme = Theme.find(params[:id])
|
2018-07-12 12:18:21 +08:00
|
|
|
redirect_to path("/?preview_theme_id=#{@theme.id}")
|
2017-04-15 01:35:12 +08:00
|
|
|
end
|
2017-04-12 22:52:52 +08:00
|
|
|
|
2017-05-10 05:20:28 +08:00
|
|
|
def upload_asset
|
|
|
|
path = params[:file].path
|
2017-12-18 07:40:10 +08:00
|
|
|
|
|
|
|
hijack do
|
|
|
|
File.open(path) do |file|
|
|
|
|
filename = params[:file]&.original_filename || File.basename(path)
|
2019-02-12 22:17:34 +08:00
|
|
|
upload = UploadCreator.new(file, filename, for_theme: true).create_for(theme_user.id)
|
2017-12-18 07:40:10 +08:00
|
|
|
if upload.errors.count > 0
|
|
|
|
render_json_error upload
|
|
|
|
else
|
|
|
|
render json: { upload_id: upload.id }, status: :created
|
|
|
|
end
|
2017-05-10 05:20:28 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-09 13:14:21 +08:00
|
|
|
def generate_key_pair
|
|
|
|
require 'sshkey'
|
|
|
|
k = SSHKey.generate
|
|
|
|
|
|
|
|
render json: {
|
|
|
|
private_key: k.private_key,
|
|
|
|
public_key: k.ssh_public_key
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2017-04-12 22:52:52 +08:00
|
|
|
def import
|
|
|
|
@theme = nil
|
2019-01-23 22:40:21 +08:00
|
|
|
if params[:theme] && params[:theme].content_type == "application/json"
|
|
|
|
# .dcstyle.json import. Deprecated, but still available to allow conversion
|
2017-12-14 18:40:14 +08:00
|
|
|
json = JSON::parse(params[:theme].read)
|
2017-04-12 22:52:52 +08:00
|
|
|
theme = json['theme']
|
|
|
|
|
2019-02-12 22:17:34 +08:00
|
|
|
@theme = Theme.new(name: theme["name"], user_id: theme_user.id)
|
2017-04-12 22:52:52 +08:00
|
|
|
theme["theme_fields"]&.each do |field|
|
2017-12-14 18:40:14 +08:00
|
|
|
|
2017-11-14 13:30:23 +08:00
|
|
|
if field["raw_upload"]
|
|
|
|
begin
|
|
|
|
tmp = Tempfile.new
|
|
|
|
tmp.binmode
|
|
|
|
file = Base64.decode64(field["raw_upload"])
|
|
|
|
tmp.write(file)
|
|
|
|
tmp.rewind
|
2019-02-12 22:17:34 +08:00
|
|
|
upload = UploadCreator.new(tmp, field["filename"]).create_for(theme_user.id)
|
2017-11-14 13:30:23 +08:00
|
|
|
field["upload_id"] = upload.id
|
|
|
|
ensure
|
|
|
|
tmp.unlink
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
@theme.set_field(
|
|
|
|
target: field["target"],
|
|
|
|
name: field["name"],
|
|
|
|
value: field["value"],
|
|
|
|
type_id: field["type_id"],
|
|
|
|
upload_id: field["upload_id"]
|
|
|
|
)
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
if @theme.save
|
|
|
|
log_theme_change(nil, @theme)
|
|
|
|
render json: @theme, status: :created
|
|
|
|
else
|
|
|
|
render json: @theme.errors, status: :unprocessable_entity
|
|
|
|
end
|
|
|
|
elsif params[:remote]
|
2018-03-09 13:14:21 +08:00
|
|
|
begin
|
2018-10-09 14:01:08 +08:00
|
|
|
branch = params[:branch] ? params[:branch] : nil
|
2019-02-12 22:17:34 +08:00
|
|
|
@theme = RemoteTheme.import_theme(params[:remote], theme_user, private_key: params[:private_key], branch: branch)
|
2018-03-09 13:14:21 +08:00
|
|
|
render json: @theme, status: :created
|
2019-01-23 22:40:21 +08:00
|
|
|
rescue RemoteTheme::ImportError => e
|
|
|
|
render_json_error e.message
|
2018-03-12 15:36:06 +08:00
|
|
|
end
|
2019-01-30 22:17:04 +08:00
|
|
|
elsif params[:bundle] || (params[:theme] && ["application/x-gzip", "application/gzip"].include?(params[:theme].content_type))
|
2019-01-23 22:40:21 +08:00
|
|
|
# params[:bundle] used by theme CLI. params[:theme] used by admin UI
|
|
|
|
bundle = params[:bundle] || params[:theme]
|
2019-01-30 22:17:04 +08:00
|
|
|
theme_id = params[:theme_id]
|
|
|
|
match_theme_by_name = !!params[:bundle] && !params.key?(:theme_id) # Old theme CLI behavior, match by name. Remove Jan 2020
|
2018-03-12 15:36:06 +08:00
|
|
|
begin
|
2019-02-12 22:17:34 +08:00
|
|
|
@theme = RemoteTheme.update_tgz_theme(bundle.path, match_theme: match_theme_by_name, user: theme_user, theme_id: theme_id)
|
2019-01-23 22:40:21 +08:00
|
|
|
log_theme_change(nil, @theme)
|
2018-03-12 15:36:06 +08:00
|
|
|
render json: @theme, status: :created
|
2019-01-23 22:40:21 +08:00
|
|
|
rescue RemoteTheme::ImportError => e
|
|
|
|
render_json_error e.message
|
2018-03-09 13:14:21 +08:00
|
|
|
end
|
2017-04-12 22:52:52 +08:00
|
|
|
else
|
2019-01-28 19:51:14 +08:00
|
|
|
render_json_error I18n.t("themes.import_error.unknown_file_type"), status: :unprocessable_entity
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def index
|
2018-12-21 01:13:05 +08:00
|
|
|
@themes = Theme.order(:name).includes(:child_themes,
|
2019-01-23 17:20:13 +08:00
|
|
|
:parent_themes,
|
2018-12-21 01:13:05 +08:00
|
|
|
:remote_theme,
|
|
|
|
:theme_settings,
|
|
|
|
:settings_field,
|
2019-02-26 22:22:02 +08:00
|
|
|
# :locale_fields, # Fails due to https://github.com/rails/rails/issues/34456
|
2018-12-21 01:13:05 +08:00
|
|
|
:user,
|
|
|
|
:color_scheme,
|
|
|
|
theme_fields: :upload
|
|
|
|
)
|
|
|
|
@color_schemes = ColorScheme.all.includes(:theme, color_scheme_colors: :color_scheme).to_a
|
2018-07-25 17:25:14 +08:00
|
|
|
light = ColorScheme.new(name: I18n.t("color_schemes.light"))
|
2017-04-12 22:52:52 +08:00
|
|
|
@color_schemes.unshift(light)
|
|
|
|
|
|
|
|
payload = {
|
2018-08-24 09:30:00 +08:00
|
|
|
themes: ActiveModel::ArraySerializer.new(@themes, each_serializer: ThemeSerializer),
|
2017-04-12 22:52:52 +08:00
|
|
|
extras: {
|
|
|
|
color_schemes: ActiveModel::ArraySerializer.new(@color_schemes, each_serializer: ColorSchemeSerializer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
respond_to do |format|
|
2017-07-28 09:20:09 +08:00
|
|
|
format.json { render json: payload }
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def create
|
|
|
|
@theme = Theme.new(name: theme_params[:name],
|
2019-02-12 22:17:34 +08:00
|
|
|
user_id: theme_user.id,
|
2017-04-12 22:52:52 +08:00
|
|
|
user_selectable: theme_params[:user_selectable] || false,
|
2018-08-24 09:30:00 +08:00
|
|
|
color_scheme_id: theme_params[:color_scheme_id],
|
|
|
|
component: [true, "true"].include?(theme_params[:component]))
|
2017-04-12 22:52:52 +08:00
|
|
|
set_fields
|
|
|
|
|
|
|
|
respond_to do |format|
|
|
|
|
if @theme.save
|
|
|
|
update_default_theme
|
|
|
|
log_theme_change(nil, @theme)
|
2017-07-28 09:20:09 +08:00
|
|
|
format.json { render json: @theme, status: :created }
|
2017-04-12 22:52:52 +08:00
|
|
|
else
|
|
|
|
format.json { render json: @theme.errors, status: :unprocessable_entity }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def update
|
|
|
|
@theme = Theme.find(params[:id])
|
|
|
|
|
|
|
|
original_json = ThemeSerializer.new(@theme, root: false).to_json
|
|
|
|
|
|
|
|
[:name, :color_scheme_id, :user_selectable].each do |field|
|
|
|
|
if theme_params.key?(field)
|
|
|
|
@theme.send("#{field}=", theme_params[field])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if theme_params.key?(:child_theme_ids)
|
|
|
|
expected = theme_params[:child_theme_ids].map(&:to_i)
|
|
|
|
|
|
|
|
@theme.child_theme_relation.to_a.each do |child|
|
|
|
|
if expected.include?(child.child_theme_id)
|
2017-07-28 09:20:09 +08:00
|
|
|
expected.reject! { |id| id == child.child_theme_id }
|
2017-04-12 22:52:52 +08:00
|
|
|
else
|
|
|
|
child.destroy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
Theme.where(id: expected).each do |theme|
|
|
|
|
@theme.add_child_theme!(theme)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
set_fields
|
2018-03-05 08:04:23 +08:00
|
|
|
update_settings
|
2019-01-17 19:46:11 +08:00
|
|
|
update_translations
|
2018-08-24 09:30:00 +08:00
|
|
|
handle_switch
|
2017-04-12 22:52:52 +08:00
|
|
|
|
2017-04-18 04:55:53 +08:00
|
|
|
save_remote = false
|
2017-04-12 22:52:52 +08:00
|
|
|
if params[:theme][:remote_check]
|
|
|
|
@theme.remote_theme.update_remote_version
|
2017-04-18 04:55:53 +08:00
|
|
|
save_remote = true
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
if params[:theme][:remote_update]
|
|
|
|
@theme.remote_theme.update_from_remote
|
2017-04-18 04:55:53 +08:00
|
|
|
save_remote = true
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
respond_to do |format|
|
|
|
|
if @theme.save
|
|
|
|
|
2017-04-18 04:55:53 +08:00
|
|
|
@theme.remote_theme.save! if save_remote
|
|
|
|
|
2017-04-12 22:52:52 +08:00
|
|
|
update_default_theme
|
|
|
|
|
2019-01-17 19:46:11 +08:00
|
|
|
@theme.reload
|
2017-04-12 22:52:52 +08:00
|
|
|
log_theme_change(original_json, @theme)
|
2018-03-05 08:04:23 +08:00
|
|
|
format.json { render json: @theme, status: :ok }
|
2017-04-12 22:52:52 +08:00
|
|
|
else
|
2018-08-08 12:46:34 +08:00
|
|
|
format.json do
|
|
|
|
error = @theme.errors.full_messages.join(", ").presence
|
|
|
|
error = I18n.t("themes.bad_color_scheme") if @theme.errors[:color_scheme].present?
|
|
|
|
error ||= I18n.t("themes.other_error")
|
2017-04-18 04:55:53 +08:00
|
|
|
|
2017-07-28 09:20:09 +08:00
|
|
|
render json: { errors: [ error ] }, status: :unprocessable_entity
|
2018-08-08 12:46:34 +08:00
|
|
|
end
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
|
|
|
end
|
2019-02-05 21:49:16 +08:00
|
|
|
rescue RemoteTheme::ImportError => e
|
|
|
|
render_json_error e.message
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def destroy
|
|
|
|
@theme = Theme.find(params[:id])
|
|
|
|
StaffActionLogger.new(current_user).log_theme_destroy(@theme)
|
|
|
|
@theme.destroy
|
|
|
|
|
|
|
|
respond_to do |format|
|
|
|
|
format.json { head :no_content }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def show
|
|
|
|
@theme = Theme.find(params[:id])
|
2019-01-23 22:40:21 +08:00
|
|
|
render json: ThemeSerializer.new(@theme)
|
|
|
|
end
|
2017-04-12 22:52:52 +08:00
|
|
|
|
2019-01-23 22:40:21 +08:00
|
|
|
def export
|
|
|
|
@theme = Theme.find(params[:id])
|
2017-04-12 22:52:52 +08:00
|
|
|
|
2019-01-23 22:40:21 +08:00
|
|
|
exporter = ThemeStore::TgzExporter.new(@theme)
|
|
|
|
file_path = exporter.package_filename
|
|
|
|
headers['Content-Length'] = File.size(file_path).to_s
|
|
|
|
send_data File.read(file_path),
|
|
|
|
filename: File.basename(file_path),
|
|
|
|
content_type: "application/x-gzip"
|
|
|
|
ensure
|
|
|
|
exporter.cleanup!
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2018-06-07 13:28:18 +08:00
|
|
|
def update_default_theme
|
|
|
|
if theme_params.key?(:default)
|
|
|
|
is_default = theme_params[:default].to_s == "true"
|
2018-07-12 12:18:21 +08:00
|
|
|
if @theme.id == SiteSetting.default_theme_id && !is_default
|
2018-06-07 13:28:18 +08:00
|
|
|
Theme.clear_default!
|
|
|
|
elsif is_default
|
|
|
|
@theme.set_default!
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2017-04-12 22:52:52 +08:00
|
|
|
|
2018-06-07 13:28:18 +08:00
|
|
|
def theme_params
|
|
|
|
@theme_params ||=
|
|
|
|
begin
|
|
|
|
# deep munge is a train wreck, work around it for now
|
|
|
|
params[:theme][:child_theme_ids] ||= [] if params[:theme].key?(:child_theme_ids)
|
|
|
|
|
|
|
|
params.require(:theme).permit(
|
|
|
|
:name,
|
|
|
|
:color_scheme_id,
|
|
|
|
:default,
|
|
|
|
:user_selectable,
|
2018-08-24 09:30:00 +08:00
|
|
|
:component,
|
2018-06-07 13:28:18 +08:00
|
|
|
settings: {},
|
2019-01-17 19:46:11 +08:00
|
|
|
translations: {},
|
2018-06-07 13:28:18 +08:00
|
|
|
theme_fields: [:name, :target, :value, :upload_id, :type_id],
|
|
|
|
child_theme_ids: []
|
2017-05-10 05:20:28 +08:00
|
|
|
)
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def set_fields
|
|
|
|
return unless fields = theme_params[:theme_fields]
|
|
|
|
|
|
|
|
fields.each do |field|
|
|
|
|
@theme.set_field(
|
|
|
|
target: field[:target],
|
|
|
|
name: field[:name],
|
|
|
|
value: field[:value],
|
|
|
|
type_id: field[:type_id],
|
|
|
|
upload_id: field[:upload_id]
|
|
|
|
)
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2017-04-12 22:52:52 +08:00
|
|
|
|
2018-06-07 13:28:18 +08:00
|
|
|
def update_settings
|
|
|
|
return unless target_settings = theme_params[:settings]
|
2018-03-05 08:04:23 +08:00
|
|
|
|
2018-06-07 13:28:18 +08:00
|
|
|
target_settings.each_pair do |setting_name, new_value|
|
|
|
|
@theme.update_setting(setting_name.to_sym, new_value)
|
2018-03-05 08:04:23 +08:00
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2018-03-05 08:04:23 +08:00
|
|
|
|
2019-01-17 19:46:11 +08:00
|
|
|
def update_translations
|
|
|
|
return unless target_translations = theme_params[:translations]
|
|
|
|
|
|
|
|
target_translations.each_pair do |translation_key, new_value|
|
|
|
|
@theme.update_translation(translation_key, new_value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-07 13:28:18 +08:00
|
|
|
def log_theme_change(old_record, new_record)
|
|
|
|
StaffActionLogger.new(current_user).log_theme_change(old_record, new_record)
|
|
|
|
end
|
2017-04-12 22:52:52 +08:00
|
|
|
|
2018-08-24 09:30:00 +08:00
|
|
|
def handle_switch
|
|
|
|
param = theme_params[:component]
|
|
|
|
if param.to_s == "false" && @theme.component?
|
|
|
|
@theme.switch_to_theme!
|
|
|
|
elsif param.to_s == "true" && !@theme.component?
|
|
|
|
@theme.switch_to_component!
|
|
|
|
end
|
|
|
|
end
|
2019-02-12 22:17:34 +08:00
|
|
|
|
|
|
|
# Overridden by theme-creator plugin
|
|
|
|
def theme_user
|
|
|
|
current_user
|
|
|
|
end
|
2017-04-12 22:52:52 +08:00
|
|
|
end
|