0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-04 10:39:43 +08:00
discourse/lib/theme_settings_parser.rb
Martin Brennan fc8df41adb
DEV: Add disallowed_groups to theme/component settings (#41792)
Further following on from bee1be8599,
this brings greater parity between theme settings and site settings.

This commit adds a new `disallowed_groups` field to theme and component
settings, which allows theme authors to specify groups that cannot be
selected
by admins for a particular list type or object group type setting.

This is useful for cases where the group-based setting isn't usable
for a group like e.g. anonymous_users, no point allowing them for
a setting that requires a user to be logged in to take effect.

c.f.
https://meta.discourse.org/t/granular-group-based-permissions-for-anonymous-and-logged-in-users/402273/18?u=martin

### List groups

With `disallowed_groups: "4|5"` specified (`logged_in_users` &
`anonymous_users`)

<img width="734" height="450" alt="image"
src="https://github.com/user-attachments/assets/ed8d7338-65fa-44e6-8dce-7073a87076e6"
/>

### Object groups

With `disallowed_groups: "4|5"` specified (`logged_in_users` &
`anonymous_users`)

<img width="696" height="725" alt="image"
src="https://github.com/user-attachments/assets/1a9103d4-cf44-4038-9d62-3838cb779b49"
/>
2026-07-20 09:28:52 +10:00

80 lines
2.4 KiB
Ruby
Vendored

# frozen_string_literal: true
class ThemeSettingsParser
class InvalidYaml < StandardError
end
def initialize(setting_field)
@setting_field = setting_field
@types = ThemeSetting.types
end
def extract_description(desc)
return desc if desc.is_a?(String)
if desc.is_a?(Hash)
default_locale = SiteSetting.default_locale.to_sym
fallback_locale = desc.keys.find { |key| I18n.locale_available?(key) }
locale = desc[I18n.locale] || desc[default_locale] || desc[:en] || desc[fallback_locale]
locale if locale.is_a?(String)
end
end
def create_opts(default, type, raw_opts = {})
opts = {}
opts[:description] = extract_description(raw_opts[:description])
if type == @types[:enum]
choices = raw_opts[:choices]
choices = [] unless choices.is_a?(Array)
choices << default if choices.exclude?(default)
opts[:choices] = choices
end
if [@types[:integer], @types[:string], @types[:float]].include?(type)
opts[:max] = raw_opts[:max].is_a?(Numeric) ? raw_opts[:max] : Float::INFINITY
opts[:min] = raw_opts[:min].is_a?(Numeric) ? raw_opts[:min] : -Float::INFINITY
end
opts[:list_type] = raw_opts[:list_type] if raw_opts[:list_type]
opts[:disallowed_groups] = raw_opts[:disallowed_groups] if raw_opts[:disallowed_groups]
opts[:resolve_group_membership] = !!raw_opts[:resolve_group_membership]
opts[:textarea] = !!raw_opts[:textarea]
opts[:json_schema] = raw_opts[:json_schema]
opts[:schema] = raw_opts[:schema]
opts[:refresh] = !!raw_opts[:refresh]
opts
end
def load
return if @setting_field.value.blank?
begin
parsed = YAML.safe_load(@setting_field.value)
rescue Psych::SyntaxError, Psych::DisallowedClass => e
raise InvalidYaml.new(e.message)
end
raise InvalidYaml.new(I18n.t("themes.settings_errors.invalid_yaml")) unless parsed.is_a?(Hash)
parsed.deep_symbolize_keys!
parsed.each_pair do |setting, value|
if (type = ThemeSetting.guess_type(value)).present?
result = [setting, value, type, create_opts(value, type)]
elsif (hash = value).is_a?(Hash)
default = hash[:default]
type = hash.key?(:type) ? @types[hash[:type]&.to_sym] : ThemeSetting.guess_type(default)
result = [setting, default, type, create_opts(default, type, hash)]
else
result = [setting, value, nil, {}]
end
yield(*result)
end
end
end