diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 index 075eba3e943..aab84d2ab92 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 @@ -81,7 +81,7 @@ export default Ember.Controller.extend({ }); }, - previewUrl: url('model.key', '/?preview-style=%@'), + previewUrl: url('model.id', '/admin/themes/%@/preview'), maximizeIcon: function() { return this.get('maximized') ? 'compress' : 'expand'; @@ -95,9 +95,6 @@ export default Ember.Controller.extend({ return !this.get('model.changed') || this.get('model.isSaving'); }.property('model.changed', 'model.isSaving'), - undoPreviewUrl: url('/?preview-style='), - defaultStyleUrl: url('/?preview-style=default'), - actions: { save() { this.get('model').saveChanges("theme_fields"); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 index 43464954584..7b9c81b7391 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 @@ -25,6 +25,8 @@ export default Ember.Controller.extend({ return descriptions.reject(d=>Em.isBlank(d)); }, + previewUrl: url('model.id', '/admin/themes/%@/preview'), + @computed("colorSchemeId", "model.color_scheme_id") colorSchemeChanged(colorSchemeId, existingId) { colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId); diff --git a/app/assets/javascripts/admin/templates/customize-themes-show.hbs b/app/assets/javascripts/admin/templates/customize-themes-show.hbs index 5bbd9cb2ee1..ae06bedc082 100644 --- a/app/assets/javascripts/admin/templates/customize-themes-show.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes-show.hbs @@ -106,6 +106,7 @@ {{/if}} {{/if}} + {{fa-icon 'desktop'}}{{i18n 'admin.customize.theme.preview'}} {{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}} {{d-button action="destroy" label="admin.customize.delete" icon="trash" class="btn-danger"}} diff --git a/app/assets/javascripts/discourse/lib/theme-selector.js.es6 b/app/assets/javascripts/discourse/lib/theme-selector.js.es6 index 40cb9eabe57..269d2d26e11 100644 --- a/app/assets/javascripts/discourse/lib/theme-selector.js.es6 +++ b/app/assets/javascripts/discourse/lib/theme-selector.js.es6 @@ -13,9 +13,9 @@ export function currentThemeKey() { export function selectDefaultTheme(key) { if (key) { - $.cookie('preview_style', key, {path: '/', expires: 9999}); + $.cookie('theme_key', key, {path: '/', expires: 9999}); } else { - $.cookie('preview_style', null, {path: '/', expires: 1}); + $.cookie('theme_key', null, {path: '/', expires: 1}); } } diff --git a/app/assets/javascripts/discourse/routes/preferences.js.es6 b/app/assets/javascripts/discourse/routes/preferences.js.es6 index 9bb0d645b15..3ff42ca52ea 100644 --- a/app/assets/javascripts/discourse/routes/preferences.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences.js.es6 @@ -13,7 +13,7 @@ export default RestrictedUserRoute.extend({ controller.setProperties({ model: user, newNameInput: user.get('name'), - selectedTheme: $.cookie('preview_style') || currentThemeKey() + selectedTheme: $.cookie('theme_key') || currentThemeKey() }); }, diff --git a/app/controllers/admin/themes_controller.rb b/app/controllers/admin/themes_controller.rb index 083cc61359f..63f160f1ba3 100644 --- a/app/controllers/admin/themes_controller.rb +++ b/app/controllers/admin/themes_controller.rb @@ -1,6 +1,12 @@ class Admin::ThemesController < Admin::AdminController - skip_before_filter :check_xhr, only: [:show] + skip_before_filter :check_xhr, only: [:show, :preview] + + def preview + @theme = Theme.find(params[:id]) + + redirect_to path("/"), flash: {preview_theme_key: @theme.key} + end def import diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f0856406b4f..4b3c1196e25 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -33,12 +33,11 @@ class ApplicationController < ActionController::Base end end + before_filter :handle_theme before_filter :set_current_user_for_logs before_filter :clear_notifications before_filter :set_locale before_filter :set_mobile_view - before_filter :inject_preview_style - before_filter :disable_customization before_filter :block_if_readonly_mode before_filter :authorize_mini_profiler before_filter :preload_json @@ -243,28 +242,40 @@ class ApplicationController < ActionController::Base session[:mobile_view] = params[:mobile_view] if params.has_key?(:mobile_view) end - def inject_preview_style - style = request['preview-style'] + NO_CUSTOM = "no_custom".freeze + NO_PLUGINS = "no_plugins".freeze + ONLY_OFFICIAL = "only_official".freeze + SAFE_MODE = "safe_mode".freeze - if style.nil? - session[:preview_style] = cookies[:preview_style] - else - cookies.delete(:preview_style) - - if style.blank? || style == 'default' - session[:preview_style] = nil - else - session[:preview_style] = style - if request['sticky'] - cookies[:preview_style] = style - end - end + def resolve_safe_mode + safe_mode = params[SAFE_MODE] + if safe_mode + request.env[NO_CUSTOM] = true if safe_mode.include?(NO_CUSTOM) + request.env[NO_PLUGINS] = true if safe_mode.include?(NO_PLUGINS) + request.env[ONLY_OFFICIAL] = true if safe_mode.include?(ONLY_OFFICIAL) end - end - def disable_customization - session[:disable_customization] = params[:customization] == "0" if params.has_key?(:customization) + def handle_theme + + return if request.xhr? || request.format.json? + return if request.method != "GET" + + resolve_safe_mode + return if request.env[NO_CUSTOM] + + theme_key = flash[:preview_theme_key] || cookies[:theme_key] || session[:theme_key] + + if theme_key && !guardian.allow_theme?(theme_key) + theme_key = nil + cookies[:theme_key] = nil + session[:theme_key] = nil + end + + theme_key ||= SiteSetting.default_theme_key + theme_key = nil if theme_key.blank? + + @theme_key = request.env[:resolved_theme_key] = theme_key end def guardian @@ -410,10 +421,14 @@ class ApplicationController < ActionController::Base def custom_html_json target = view_context.mobile_view? ? :mobile : :desktop - data = { - top: Theme.lookup_field(session[:preview_style], target, "after_header"), - footer: Theme.lookup_field(session[:preview_style], target, "footer") - } + data = if @theme_key + { + top: Theme.lookup_field(@theme_key, target, "after_header"), + footer: Theme.lookup_field(@theme_key, target, "footer") + } + else + {} + end if DiscoursePluginRegistry.custom_html data.merge! DiscoursePluginRegistry.custom_html diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 74ad085e700..67651cc0a55 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -248,32 +248,23 @@ module ApplicationHelper MobileDetection.mobile_device?(request.user_agent) end - NO_CUSTOM = "no_custom".freeze - NO_PLUGINS = "no_plugins".freeze - ONLY_OFFICIAL = "only_official".freeze - SAFE_MODE = "safe_mode".freeze - def customization_disabled? - safe_mode = params[SAFE_MODE] - session[:disable_customization] || (safe_mode && safe_mode.include?(NO_CUSTOM)) + request.env[ApplicationController::NO_CUSTOM] end def allow_plugins? - safe_mode = params[SAFE_MODE] - !(safe_mode && safe_mode.include?(NO_PLUGINS)) + !request.env[ApplicationController::NO_PLUGINS] end def allow_third_party_plugins? - safe_mode = params[SAFE_MODE] - !(safe_mode && (safe_mode.include?(NO_PLUGINS) || safe_mode.include?(ONLY_OFFICIAL))) + allow_plugins? && !request.env[ApplicationController::ONLY_OFFICIAL] end def normalized_safe_mode - mode_string = params["safe_mode"] safe_mode = nil - (safe_mode ||= []) << NO_CUSTOM if mode_string.include?(NO_CUSTOM) - (safe_mode ||= []) << NO_PLUGINS if mode_string.include?(NO_PLUGINS) - (safe_mode ||= []) << ONLY_OFFICIAL if mode_string.include?(ONLY_OFFICIAL) + (safe_mode ||= []) << ApplicationController::NO_CUSTOM if customization_disabled? + (safe_mode ||= []) << ApplicationController::NO_PLUGINS if !allow_plugins? + (safe_mode ||= []) << ApplicationController::ONLY_OFFICIAL if !allow_third_party_plugins? if safe_mode safe_mode.join(",").html_safe end @@ -321,7 +312,7 @@ module ApplicationHelper if customization_disabled? nil else - session[:preview_style] || SiteSetting.default_theme_key + request.env[:resolved_theme_key] end end diff --git a/app/models/theme.rb b/app/models/theme.rb index 28c7b09cb71..06a8bb40fe2 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -45,6 +45,24 @@ class Theme < ActiveRecord::Base theme.notify_theme_change end, on: :update + def self.theme_keys + if keys = @cache["theme_keys"] + return keys + end + @cache["theme_keys"] = Set.new(Theme.pluck(:key)) + end + + def self.user_theme_keys + if keys = @cache["user_theme_keys"] + return keys + end + @cache["theme_keys"] = Set.new( + Theme + .where('user_selectable OR key = ?', SiteSetting.default_theme_key) + .pluck(:key) + ) + end + def self.expire_site_cache! Site.clear_anon_cache! ApplicationSerializer.expire_cache_fragment!("user_themes") diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 95c82c25645..7e19ed74f18 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2785,7 +2785,7 @@ en: title: "Customize" long_title: "Site Customizations" preview: "preview" - explain_preview: "See the site with this custom stylesheet" + explain_preview: "See the site with this theme enabled" save: "Save" new: "New" new_style: "New Style" @@ -2815,6 +2815,7 @@ en: common: "Common" desktop: "Desktop" mobile: "Mobile" + preview: "Preview" is_default: "Theme is enabled by default" user_selectable: "Theme can be selected by users" color_scheme: "Color Scheme" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 1ff8f33edbf..8cf969d5b7b 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -3361,7 +3361,7 @@ en: safe_mode: title: "Enter safe mode" description: "Safe mode allows you to test your site without loading plugins or site customizations." - no_customizations: "Disable all site customizations" + no_customizations: "Disable current theme" only_official: "Disable unofficial plugins" no_plugins: "Disable all plugins" enter: "Enter Safe Mode" diff --git a/config/routes.rb b/config/routes.rb index c534d2d41ec..2f0c9ef3584 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -188,6 +188,7 @@ Discourse::Application.routes.draw do resources :themes, constraints: AdminConstraint.new post "themes/import" => "themes#import" + get "themes/:id/preview" => "themes#preview" scope "/customize", constraints: AdminConstraint.new do resources :user_fields, constraints: AdminConstraint.new diff --git a/lib/guardian.rb b/lib/guardian.rb index 4a369938849..1f00960b9bd 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -297,6 +297,14 @@ class Guardian UserExport.where(user_id: @user.id, created_at: (Time.zone.now.beginning_of_day..Time.zone.now.end_of_day)).count == 0 end + def allow_theme?(theme_key) + if is_staff? + Theme.theme_keys.include?(theme_key) + else + Theme.theme_user_keys.include?(theme_key) + end + end + private diff --git a/spec/models/theme_spec.rb b/spec/models/theme_spec.rb index 6e11d7a5a5d..e75bdb5d8a9 100644 --- a/spec/models/theme_spec.rb +++ b/spec/models/theme_spec.rb @@ -137,5 +137,28 @@ HTML end end + it 'correctly caches theme keys' do + theme = Theme.create!(name: "bob", user_id: -1) + + expect(Theme.theme_keys).to eq(Set.new([theme.key])) + expect(Theme.user_theme_keys).to eq(Set.new([])) + + theme.user_selectable = true + theme.save + + expect(Theme.user_theme_keys).to eq(Set.new([theme.key])) + + theme.user_selectable = false + theme.save + + theme.set_default! + expect(Theme.user_theme_keys).to eq(Set.new([theme.key])) + + theme.destroy + + expect(Theme.theme_keys).to eq(Set.new([])) + expect(Theme.user_theme_keys).to eq(Set.new([])) + end + end