mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 04:02:30 +08:00
Previously, on sites that require staff to approve new members, moderators were not reminded about people waiting for approval until 8 hours (480 minutes) after they registered, leaving new members waiting far longer than necessary. This change adds an `update_pending_users_reminder_default` upcoming change that lowers the `pending_users_reminder_delay_minutes` default to 30 when enabled, so staff are notified promptly. It uses the virtual default-override mechanism rather than writing to the DB, so an admin who has customized the setting keeps their value and disabling the change restores the previous default. The change is only surfaced on sites with `must_approve_users` enabled, since the reminder never fires otherwise.
606 lines
24 KiB
Ruby
Vendored
606 lines
24 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module UpcomingChanges
|
|
# Some upcoming changes make no sense to display to admins,
|
|
# for example ones related to Horizon theme makes no sense to
|
|
# display if Horizon is not installed or is disabled, a change
|
|
# might modify how site behavior works if another setting is enabled,
|
|
# and so on.
|
|
#
|
|
# Core can define any should_display_<upcoming_change_name>? method to control
|
|
# whether an upcoming change should be displayed to admins. Plugins can use
|
|
# Plugin::Instance#register_upcoming_change_conditional_display for their own
|
|
# upcoming changes. If no conditional display rule is defined, the change will
|
|
# always be displayed.
|
|
#
|
|
# A plugin-owned change is hidden, and never takes effect, while its owning
|
|
# plugin is disabled -- unless it opts out with `requires_plugin_enabled: false`.
|
|
# See .owning_plugin_enabled?
|
|
#
|
|
# Keep in mind this is called from UpcomingChanges::List service,
|
|
# which loops over every change in an N1 depending on the filters admins
|
|
# have selected, so caching may be appropriate at times.
|
|
class ConditionalDisplay
|
|
def self.should_display?(upcoming_change_name)
|
|
upcoming_change_name = upcoming_change_name.to_sym
|
|
|
|
return false if !UpcomingChanges.owning_plugin_configurable?(upcoming_change_name)
|
|
return false if !UpcomingChanges.owning_plugin_enabled?(upcoming_change_name)
|
|
|
|
if respond_to?("should_display_#{upcoming_change_name}?")
|
|
return public_send("should_display_#{upcoming_change_name}?")
|
|
end
|
|
|
|
callbacks =
|
|
DiscoursePluginRegistry.upcoming_change_conditional_display_callbacks.select do |callback|
|
|
callback[:setting_name] == upcoming_change_name
|
|
end
|
|
|
|
callbacks.empty? || callbacks.all? { |callback| callback[:callback].call }
|
|
end
|
|
|
|
def self.should_display_enable_horizon_high_context_topic_cards?
|
|
Themes::Action::HorizonHighContextTopicCardsToggled.should_display_upcoming_change?
|
|
end
|
|
|
|
# Only relevant on sites that currently allow uncategorized topics, and must
|
|
# stay visible after being enabled (which disables that setting).
|
|
def self.should_display_remove_and_replace_uncategorized?
|
|
SiteSetting::Action::RemoveAndReplaceUncategorizedToggled.should_display_upcoming_change?
|
|
end
|
|
|
|
# Code login is a delivery variant of email login (see
|
|
# EnableLocalLoginsViaCodeValidator), so the change is only actionable when
|
|
# local logins via email are possible. Must stay visible once enabled so
|
|
# admins can still find and disable it.
|
|
def self.should_display_enable_local_logins_via_code?
|
|
return true if UpcomingChanges.enabled?(:enable_local_logins_via_code)
|
|
|
|
SiteSetting.enable_local_logins && SiteSetting.enable_local_logins_via_email &&
|
|
!SiteSetting.enable_discourse_connect
|
|
end
|
|
|
|
# Pending-user reminders only fire when must_approve_users is on, so the
|
|
# change is irrelevant elsewhere, including when enabled: the overridden
|
|
# default has no effect on a site where nobody can be pending.
|
|
def self.should_display_update_pending_users_reminder_default?
|
|
SiteSetting.must_approve_users
|
|
end
|
|
end
|
|
|
|
def self.user_enabled_reasons
|
|
@user_enabled_reasons ||=
|
|
::Enum.new(
|
|
enabled_for_everyone: :enabled_for_everyone,
|
|
enabled_for_no_one: :enabled_for_no_one,
|
|
in_specific_groups: :in_specific_groups,
|
|
not_in_specific_groups: :not_in_specific_groups,
|
|
)
|
|
end
|
|
|
|
def self.statuses
|
|
@statuses ||=
|
|
::Enum.new(
|
|
conceptual: -100,
|
|
experimental: 0,
|
|
alpha: 100,
|
|
beta: 200,
|
|
stable: 300,
|
|
permanent: 500,
|
|
never: 9999,
|
|
)
|
|
end
|
|
|
|
# Mostly used for testing, to allow stubbing the SiteSetting provider,
|
|
# like for SiteSettingExtension spec. This is not ideal, but the SiteSettingExtension spec
|
|
# is extremely gnarly.
|
|
def self.settings_provider
|
|
SiteSetting
|
|
end
|
|
|
|
def self.previous_status_value(status)
|
|
status_value = statuses[status.to_sym]
|
|
statuses.values.select { |value| value < status_value }.max || -100
|
|
end
|
|
|
|
def self.previous_status(status)
|
|
statuses.keys.select { |key| statuses[key] < statuses[status.to_sym] }.last || :conceptual
|
|
end
|
|
|
|
def self.next_status(status)
|
|
status = status&.to_sym
|
|
status_value = statuses[status]
|
|
|
|
return if status_value.nil?
|
|
return if status_value < statuses[:experimental] || status_value >= statuses[:stable]
|
|
|
|
statuses.keys.find { |key| statuses[key] > status_value }
|
|
end
|
|
|
|
def self.image_exists?(change_setting_name)
|
|
File.exist?(File.join(Rails.public_path, image_path(change_setting_name)))
|
|
end
|
|
|
|
def self.image_path(change_setting_name)
|
|
plugin_name = settings_provider.plugins[change_setting_name.to_sym]
|
|
if plugin_name.present?
|
|
File.join("plugins", plugin_name, "images", "upcoming_changes", "#{change_setting_name}.png")
|
|
else
|
|
File.join("images", "upcoming_changes", "#{change_setting_name}.png")
|
|
end
|
|
end
|
|
|
|
def self.image_data(change_setting_name, include_file_path: false)
|
|
width, height = nil, nil
|
|
|
|
full_file_path = File.join(Rails.public_path, image_path(change_setting_name))
|
|
|
|
File.open(full_file_path, "rb") do |file|
|
|
image_info = FastImage.new(file)
|
|
width, height = image_info.size
|
|
end
|
|
|
|
data = { url: "#{Discourse.base_url}/#{image_path(change_setting_name)}", width:, height: }
|
|
|
|
data[:file_path] = full_file_path if include_file_path
|
|
|
|
data
|
|
end
|
|
|
|
def self.change_metadata(change_setting_name)
|
|
change_setting_name = change_setting_name.to_sym
|
|
settings_provider.upcoming_change_metadata[change_setting_name] || {}
|
|
end
|
|
|
|
def self.not_yet_stable?(change_setting_name)
|
|
change_status_value(change_setting_name) < UpcomingChanges.statuses[:stable]
|
|
end
|
|
|
|
def self.stable_or_permanent?(change_setting_name)
|
|
change_status_value(change_setting_name) >= UpcomingChanges.statuses[:stable]
|
|
end
|
|
|
|
def self.meets_or_exceeds_status?(change_setting_name, status)
|
|
change_status_value(change_setting_name) >= UpcomingChanges.statuses[status]
|
|
end
|
|
|
|
def self.change_status_value(change_setting_name)
|
|
UpcomingChanges.statuses[change_status(change_setting_name)]
|
|
end
|
|
|
|
def self.change_status(change_setting_name)
|
|
change_metadata(change_setting_name)[:status]
|
|
end
|
|
|
|
def self.history_for(change_setting_name)
|
|
change_setting_name = change_setting_name.to_sym
|
|
UserHistory.where(
|
|
action: UserHistory.actions[:upcoming_change_toggled],
|
|
subject: change_setting_name,
|
|
).order(created_at: :desc)
|
|
end
|
|
|
|
def self.exists?(change_setting_name)
|
|
change_metadata(change_setting_name.to_sym).present?
|
|
end
|
|
|
|
# An upcoming change owned by a plugin that is not configurable on this site,
|
|
# is never available. It must not be displayed, notified about, or enabled.
|
|
#
|
|
# This is deliberately broader than the guard in SiteSettingExtension#setting,
|
|
# which only forces a plugin's own enabled_site_setting to false. A change
|
|
# gating a sub-feature of an unavailable plugin is equally unavailable.
|
|
#
|
|
# Core changes have no owning plugin and return early, so the common case
|
|
# never reaches #configurable? and adds no cost to callers on hot paths like
|
|
# .settings_hidden_while_enabled.
|
|
def self.owning_plugin_configurable?(change_setting_name)
|
|
plugin_name = settings_provider.plugins[change_setting_name.to_sym]
|
|
return true if plugin_name.nil?
|
|
Discourse.plugins_by_name[plugin_name]&.configurable? != false
|
|
end
|
|
|
|
# Whether a plugin-owned change is gated on the plugin being enabled.
|
|
# By default: upcoming changes in plugins are neither displayed nor take effect.
|
|
# A plugin change can opt OUT with `requires_plugin_enabled: false`.
|
|
def self.requires_plugin_enabled?(change_setting_name)
|
|
change_metadata(change_setting_name)[:requires_plugin_enabled] != false
|
|
end
|
|
|
|
def self.owning_plugin_enabled?(change_setting_name)
|
|
change_setting_name = change_setting_name.to_sym
|
|
return true if !requires_plugin_enabled?(change_setting_name)
|
|
|
|
plugin_name = settings_provider.plugins[change_setting_name]
|
|
return true if plugin_name.nil?
|
|
|
|
plugin = Discourse.plugins_by_name[plugin_name]
|
|
return true if plugin.nil?
|
|
|
|
# Recursion guard: a change that is its own plugin's enabled_site_setting
|
|
# must opt out with requires_plugin_enabled: false (enforced by the
|
|
# integrity spec), but if that metadata is forgotten, plugin.enabled? below
|
|
# would read the change setting, which resolves back through
|
|
# UpcomingChanges.enabled? and recurses infinitely.
|
|
return true if plugin.enabled_site_setting&.to_sym == change_setting_name
|
|
|
|
plugin.enabled? != false
|
|
end
|
|
|
|
# We dynamically determine if an upcoming change is enabled
|
|
# or disabled based on the current status of the change as well
|
|
# as whether the admin has manually toggled the change.
|
|
#
|
|
# @param change_setting_name [Symbol] The name of the upcoming change
|
|
# @return [Boolean]
|
|
def self.enabled?(change_setting_name)
|
|
change_setting_name = change_setting_name.to_sym
|
|
|
|
if !exists?(change_setting_name)
|
|
raise ArgumentError, "Unknown upcoming change: #{change_setting_name}"
|
|
end
|
|
|
|
# The owning plugin is not available on this site, so neither is the change.
|
|
# This intentionally takes precedence over the :permanent status below, since
|
|
# a permanent change to an unavailable plugin still cannot take effect.
|
|
return false if !owning_plugin_configurable?(change_setting_name)
|
|
|
|
# The owning plugin is disabled, so the change must not take effect either.
|
|
# Without this guard an opted-in change would keep acting on the site after
|
|
# its plugin is switched off -- its `hide_settings` stay hidden and its
|
|
# default overrides stay applied -- while ConditionalDisplay (which also
|
|
# checks this) hides the change from admins, leaving them no way to see
|
|
# what is causing those effects. The stored opt-in is deliberately left
|
|
# untouched, so the change resumes when the plugin is re-enabled.
|
|
return false if !owning_plugin_enabled?(change_setting_name)
|
|
|
|
# An admin has modified the setting and a value is stored
|
|
# in the database, since the default for upcoming changes
|
|
# is false.
|
|
#
|
|
# If the change is permanent though, the admin has no choice
|
|
# in the matter.
|
|
if settings_provider.setting_modified_from_default?(change_setting_name) &&
|
|
UpcomingChanges.change_status(change_setting_name) != :permanent
|
|
settings_provider.current[change_setting_name]
|
|
|
|
# The change has reached the promotion status and is forcibly
|
|
# enabled, admins can still disable it.
|
|
elsif UpcomingChanges.meets_or_exceeds_status?(
|
|
change_setting_name,
|
|
settings_provider.promote_upcoming_changes_on_status.to_sym,
|
|
) || UpcomingChanges.change_status(change_setting_name) == :permanent
|
|
true
|
|
else
|
|
# Otherwise use the default value, which for upcoming changes
|
|
# is false.
|
|
settings_provider.defaults[change_setting_name]
|
|
end
|
|
end
|
|
|
|
# The `allow_enabled_for` metadata for an upcoming change, or nil if unset.
|
|
# When nil, every "Enabled for" dropdown option is permitted. Otherwise it
|
|
# is an array containing any subset of [:everyone, :staff, :specific_groups].
|
|
def self.allow_enabled_for(change_setting_name)
|
|
change_metadata(change_setting_name)[:allow_enabled_for]
|
|
end
|
|
|
|
# Whether a setting's `allow_enabled_for` permits a given dropdown target.
|
|
# `:no_one` is always allowed. Returns true when the metadata is absent.
|
|
def self.target_allowed?(change_setting_name, target)
|
|
return true if target.to_sym == :no_one
|
|
allow = allow_enabled_for(change_setting_name)
|
|
return true if allow.nil?
|
|
allow.include?(target.to_sym)
|
|
end
|
|
|
|
# True when the setting's `allow_enabled_for` permits any group-based target
|
|
# (`:staff` or `:specific_groups`). When metadata is absent, groups are allowed.
|
|
def self.groups_target_allowed?(change_setting_name)
|
|
allow = allow_enabled_for(change_setting_name)
|
|
return true if allow.nil?
|
|
allow.include?(:staff) || allow.include?(:specific_groups)
|
|
end
|
|
|
|
def self.has_groups?(change_setting_name)
|
|
group_ids_for(change_setting_name).present?
|
|
end
|
|
|
|
def self.group_ids_for(change_setting_name)
|
|
change_setting_name = change_setting_name.to_sym
|
|
settings_provider.site_setting_group_ids[change_setting_name].presence || []
|
|
end
|
|
|
|
# Checks if a given upcoming change is enabled for a user,
|
|
# which can be either enabled for everyone, enabled for certain groups,
|
|
# or disabled for everyone. The user's group membership is used to determine
|
|
# if the upcoming change is enabled for them if the upcoming change is
|
|
# enabled for certain groups.
|
|
#
|
|
# @param change_setting_name [Symbol] The name of the upcoming change
|
|
# @param user [User] The user to check if the upcoming change is enabled for
|
|
# @return [Boolean]
|
|
def self.enabled_for_user?(change_setting_name, user)
|
|
change_setting_name = change_setting_name.to_sym
|
|
setting_enabled = UpcomingChanges.enabled?(change_setting_name)
|
|
|
|
# Anon users can only have upcoming changes enabled if it's set for Everyone
|
|
if user.blank?
|
|
return false if UpcomingChanges.has_groups?(change_setting_name)
|
|
else
|
|
if UpcomingChanges.has_groups?(change_setting_name)
|
|
return(
|
|
setting_enabled && user.in_any_groups?(UpcomingChanges.group_ids_for(change_setting_name))
|
|
)
|
|
end
|
|
end
|
|
|
|
setting_enabled
|
|
end
|
|
|
|
# Calculates the current state of all upcoming changes for a given user,
|
|
# including the reason why a change is or isn't enabled for them, and
|
|
# if it's due to group membership, which groups are relevant.
|
|
#
|
|
# The acting_guardian is used to determine group visibility. This is
|
|
# mostly used to show a list of upcoming changes for a user in the admin
|
|
# interface.
|
|
#
|
|
# @param user [User] The user to get the upcoming changes for
|
|
# @param acting_guardian [Guardian] The current user's guardian
|
|
# @return [Array<Hash>]
|
|
#
|
|
# @example
|
|
# stats_for_user(user: user, acting_guardian: admin)
|
|
# # => [
|
|
# # {
|
|
# # name: "new_feature",
|
|
# # humanized_name: "New Feature",
|
|
# # description: "This is a new feature",
|
|
# # enabled: true,
|
|
# # specific_groups: ["Group 1", "Group 2"],
|
|
# # reason: :in_specific_groups
|
|
# # },
|
|
# # {
|
|
# # name: "another_feature",
|
|
# # humanized_name: "Another Feature",
|
|
# # description: "This is another feature",
|
|
# # enabled: false,
|
|
# # specific_groups: [],
|
|
# # reason: :enabled_for_no_one
|
|
# # },
|
|
# # ]
|
|
def self.stats_for_user(user:, acting_guardian:)
|
|
guardian_visible_group_ids = Group.visible_groups(acting_guardian.user).pluck(:id)
|
|
user_belonging_to_group_ids = user.belonging_to_group_ids
|
|
|
|
settings_provider.upcoming_change_site_settings.filter_map do |name|
|
|
next if UpcomingChanges.change_status(name) == :conceptual
|
|
enabled = user.upcoming_change_enabled?(name)
|
|
has_groups = UpcomingChanges.has_groups?(name)
|
|
|
|
specific_groups = []
|
|
reason =
|
|
if has_groups
|
|
visible_group_ids =
|
|
UpcomingChanges.group_ids_for(name) & guardian_visible_group_ids &
|
|
user_belonging_to_group_ids
|
|
|
|
specific_groups = Group.where(id: visible_group_ids).pluck(:name)
|
|
if enabled
|
|
UpcomingChanges.user_enabled_reasons[:in_specific_groups]
|
|
else
|
|
UpcomingChanges.user_enabled_reasons[:not_in_specific_groups]
|
|
end
|
|
elsif enabled
|
|
UpcomingChanges.user_enabled_reasons[:enabled_for_everyone]
|
|
else
|
|
UpcomingChanges.user_enabled_reasons[:enabled_for_no_one]
|
|
end
|
|
|
|
{
|
|
name:,
|
|
humanized_name: settings_provider.humanized_name(name),
|
|
description: settings_provider.description(name),
|
|
enabled:,
|
|
specific_groups:,
|
|
reason:,
|
|
}
|
|
end
|
|
end
|
|
|
|
# For a given setting, we need to determine the enabled for value
|
|
# for the UI based on the setting value, and if the setting is enabled
|
|
# for certain groups, we need the actual group records to display in the UI.
|
|
# Mostly a utility method.
|
|
#
|
|
# @param setting_name [Symbol] The name of the setting
|
|
# @param setting_value [Boolean] The value of the setting
|
|
# @param upcoming_change_selected_groups [Hash] A hash of group ids to group names
|
|
# across all upcoming changes.
|
|
# @return [Hash] The enabled for value and the setting groups
|
|
#
|
|
# @example
|
|
# enabled_for_with_groups(:new_feature, true, { 1 => "Group 1", 2 => "Group 2" })
|
|
def self.enabled_for_with_groups(setting_name, setting_value, upcoming_change_selected_groups)
|
|
group_ids_for_setting = settings_provider.site_setting_group_ids[setting_name]
|
|
setting_groups =
|
|
upcoming_change_selected_groups.values_at(*group_ids_for_setting).join(
|
|
",",
|
|
) if group_ids_for_setting.present?
|
|
|
|
enabled_for =
|
|
if !setting_value
|
|
"no_one"
|
|
elsif setting_groups.blank?
|
|
# When `allow_enabled_for` excludes `:everyone` and the change is enabled
|
|
# without an admin-configured scope (typically because it was auto-promoted
|
|
# past the promotion threshold) we surface the broadest allowed target as
|
|
# the dropdown's selected value, since `"everyone"` is no longer a valid
|
|
# option. Backend access (`enabled_for_user?`) is unchanged — until the
|
|
# admin picks a scope, the change is still effectively on for everyone.
|
|
allow = allow_enabled_for(setting_name)
|
|
if allow.nil? || allow.include?(:everyone)
|
|
"everyone"
|
|
elsif allow.include?(:staff)
|
|
# Have to do this because the staff auto group name is localized
|
|
upcoming_change_selected_groups[Group::AUTO_GROUPS[:staff]]
|
|
else
|
|
"groups"
|
|
end
|
|
else
|
|
if group_ids_for_setting == [Group::AUTO_GROUPS[:staff]]
|
|
# Have to do this because the staff auto group name is localized
|
|
upcoming_change_selected_groups[Group::AUTO_GROUPS[:staff]]
|
|
else
|
|
"groups"
|
|
end
|
|
end
|
|
|
|
{ enabled_for:, setting_groups: }
|
|
end
|
|
|
|
def self.clear_caches!
|
|
Discourse.cache.delete(current_statuses_cache_key)
|
|
Discourse.cache.delete(permanent_upcoming_changes_cache_key)
|
|
DiscourseUpdates.clear_latest_new_feature_created_at_cache
|
|
end
|
|
|
|
def self.current_statuses_cache_key
|
|
"upcoming_changes_current_statuses::#{Discourse.git_version}"
|
|
end
|
|
|
|
# This also only changes once per deploy, so we can cache to the git version
|
|
# to save time in other places in the codebase when we have to figure out
|
|
# when an upcoming change moved to its current status.
|
|
#
|
|
# This cache is automatically cleared when UpcomingChanges::Action::TrackStatusChanges
|
|
# is called, since that adds new UpcomingChangeEvent records.
|
|
def self.current_statuses
|
|
Discourse
|
|
.cache
|
|
.fetch(current_statuses_cache_key) do
|
|
results = DB.query(<<-SQL, status_changed: UpcomingChangeEvent.event_types[:status_changed])
|
|
WITH latest_status_changes AS (
|
|
SELECT upcoming_change_name, MAX(created_at) as created_at
|
|
FROM upcoming_change_events
|
|
WHERE event_type = :status_changed
|
|
GROUP BY upcoming_change_name
|
|
ORDER BY MAX(created_at) DESC
|
|
)
|
|
SELECT latest_status_changes.upcoming_change_name, latest_status_changes.created_at, upcoming_change_events.event_data->>'new_value' as new_value
|
|
FROM latest_status_changes
|
|
INNER JOIN upcoming_change_events ON upcoming_change_events.upcoming_change_name = latest_status_changes.upcoming_change_name AND upcoming_change_events.created_at = latest_status_changes.created_at
|
|
ORDER BY latest_status_changes.created_at DESC
|
|
SQL
|
|
|
|
results.each_with_object({}) do |result, statuses|
|
|
statuses[result.upcoming_change_name] = {
|
|
status: result.new_value,
|
|
changed_at: result.created_at,
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.permanent_upcoming_changes_cache_key
|
|
"upcoming_changes_permanent::#{Discourse.git_version}"
|
|
end
|
|
|
|
# These don't change except on deploy, so we can cache to the git version
|
|
# to save time in other places in the codebase when we have to figure out
|
|
# whether a change is permanent or not.
|
|
def self.permanent_upcoming_changes
|
|
Discourse
|
|
.cache
|
|
.fetch(permanent_upcoming_changes_cache_key) do
|
|
result =
|
|
UpcomingChanges::List.call(
|
|
guardian: Discourse.system_user.guardian,
|
|
options: {
|
|
filter_statuses: [:permanent],
|
|
},
|
|
)
|
|
|
|
if !result.success? && Rails.env.local?
|
|
puts result.inspect_steps
|
|
raise
|
|
end
|
|
|
|
result.upcoming_changes
|
|
end
|
|
end
|
|
|
|
# The setting names of all permanent upcoming changes. Used on the frontend
|
|
# to decide whether a notification should link to the upcoming changes page
|
|
# or to the What's New page (where permanent changes are surfaced).
|
|
def self.permanent_upcoming_change_names
|
|
permanent_upcoming_changes.map { |uc| uc[:setting].to_s }
|
|
end
|
|
|
|
# No point in notifying admins on brand new sites, the upcoming change system
|
|
# is more about notifying admins of changes to established sites.
|
|
#
|
|
# Of course we don't care about this in development, we need to test notifications,
|
|
# and we can stub this method in rspec.
|
|
def self.should_notify_admins?
|
|
Migration::Helpers.existing_site? || Rails.env.development?
|
|
end
|
|
|
|
# Some upcoming changes have a depends_on relationship with other settings,
|
|
# where it doesn't make sense to show the dependent settings in the site
|
|
# settings UI unless the upcoming change is enabled.
|
|
#
|
|
# This is done via depends_on and depends_behavior: hidden in site_settings.yml.
|
|
def self.find_dependents_for_change(change_setting_name)
|
|
settings_provider.type_supervisor.dependencies.dependents(change_setting_name.to_s)
|
|
end
|
|
|
|
# Whether the settings the change itself depends_on (in site_settings.yml)
|
|
# currently hold the values the change needs. Used by the admin UI to warn
|
|
# admins when a change's prerequisites are not met, since enabling the change
|
|
# would have no effect (or be rejected by a validator) until they are.
|
|
def self.change_dependencies_met?(change_setting_name)
|
|
dependencies = settings_provider.type_supervisor.dependencies[change_setting_name.to_sym]
|
|
return true if dependencies.blank?
|
|
|
|
allowed_values = settings_provider.dependency_values[change_setting_name.to_sym]
|
|
dependencies.all? do |dependency|
|
|
value = settings_provider.public_send(dependency)
|
|
if (allowed = allowed_values&.dig(dependency))
|
|
allowed.include?(value.to_s)
|
|
else
|
|
value == true
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.including_css
|
|
settings_provider.upcoming_change_site_settings.filter_map do |upcoming_change|
|
|
upcoming_change if settings_provider.upcoming_change_metadata[upcoming_change][:body_class]
|
|
end
|
|
end
|
|
|
|
# Site settings to hide from admins because an upcoming change that declares
|
|
# `hide_settings:` (in its site_settings.yml metadata) is currently enabled.
|
|
# Legacy settings a change replaces stop making sense once the change is in
|
|
# effect, so they are hidden while it is enabled.
|
|
#
|
|
# Consulted by SiteSettings::HiddenProvider#all, which runs on every
|
|
# hidden_settings read. It is computed live rather than toggled at opt-in time
|
|
# so it tracks both opt-in paths (manual and auto-promotion) and is
|
|
# multisite-safe: the hidden set is process-global, but enabled? resolves
|
|
# per-site, so we never hide a setting for sites that haven't opted in.
|
|
#
|
|
# `enabled?` is only called for the (usually zero) changes that declare
|
|
# `hide_settings`, so the common case is a cheap metadata scan with no DB hit.
|
|
def self.settings_hidden_while_enabled
|
|
metadata = settings_provider.upcoming_change_metadata
|
|
return [] if metadata.empty?
|
|
|
|
metadata.each_with_object([]) do |(change_name, change_metadata), hidden|
|
|
next if change_metadata[:hide_settings].blank?
|
|
hidden.concat(change_metadata[:hide_settings]) if enabled?(change_name)
|
|
end
|
|
end
|
|
end
|