2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-03-04 01:15:08 +08:00
discourse/app/serializers/site_serializer.rb
Kris 9f7d0d34c6
UX: when SMTP is not configured, show email disabled banner selectively (#38002)
This is a follow-up to
2a6fd61af3

Since it's now possible to run a Discourse site without configuring
SMTP, we can handle warnings about email a little differently.

When SMTP is not configured, we can show a more specific banner in areas
where email is relevant:
<img width="2190" height="218" alt="image"
src="https://github.com/user-attachments/assets/7e379fb0-32e0-4c52-8b2b-83bddc4573a1"
/>

When SMTP is not configured, this will appear on relevant routes: 

* admin 
* review
* login (covers forgotten email) 
* signup confirmation after account creation
* user preferences: email, notifications, security 
* invite pages (both generating and accepting invites) 

The SMTP warning will not appear in development mode. 

When email is explicitly disabled using the `Disable emails` site
setting, this banner will still appear globally:

<img width="2212" height="232" alt="image"
src="https://github.com/user-attachments/assets/4e088534-0989-4f8a-811d-8ebcbefb99d4"
/>
2026-02-24 12:25:01 -05:00

441 lines
9.9 KiB
Ruby

# frozen_string_literal: true
class SiteSerializer < ApplicationSerializer
include NavigationMenuTagsMixin
attributes(
:default_archetype,
:notification_types,
:post_types,
:user_tips,
:trust_levels,
:groups,
:filters,
:periods,
:top_menu_items,
:anonymous_top_menu_items,
:uncategorized_category_id, # this is hidden so putting it here
:user_field_max_length,
:post_action_types,
:topic_flag_types,
:can_create_tag,
:can_tag_topics,
:can_tag_pms,
:tags_filter_regexp,
:top_tags,
:navigation_menu_site_top_tags,
:can_associate_groups,
:wizard_required,
:topic_featured_link_allowed_category_ids,
:user_themes,
:user_color_schemes,
:default_light_color_scheme,
:default_dark_color_scheme,
:censored_regexp,
:shared_drafts_category_id,
:custom_emoji_translation,
:watched_words_replace,
:watched_words_link,
:categories,
:markdown_additional_options,
:hashtag_configurations,
:hashtag_icons,
:anonymous_default_navigation_menu_tags,
:anonymous_sidebar_sections,
:whispers_allowed_groups_names,
:denied_emojis,
:tos_url,
:privacy_policy_url,
:system_user_avatar_template,
:lazy_load_categories,
:valid_flag_applies_to_types,
:full_name_required_for_signup,
:full_name_visible_in_signup,
:admin_config_login_routes,
:email_configured,
)
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
has_many :user_fields, embed: :objects, serializer: UserFieldSerializer
has_many :auth_providers, embed: :objects, serializer: AuthProviderSerializer
has_many :anonymous_sidebar_sections, embed: :objects, serializer: SidebarSectionSerializer
def user_themes
cache_fragment("user_themes") do
Theme
.where("id = :default OR user_selectable", default: SiteSetting.default_theme_id)
.order("lower(name)")
.pluck(:id, :name, :color_scheme_id, :dark_color_scheme_id)
.map do |id, name, color_scheme_id, dark_color_scheme_id|
{
theme_id: id,
name: name,
default: id == SiteSetting.default_theme_id,
color_scheme_id: color_scheme_id,
dark_color_scheme_id: dark_color_scheme_id,
}
end
.as_json
end
end
def user_color_schemes
cache_fragment("user_color_schemes") do
schemes = ColorScheme.includes(:color_scheme_colors).where("user_selectable").order(:name)
ActiveModel::ArraySerializer.new(
schemes,
each_serializer: ColorSchemeSelectableSerializer,
).as_json
end
end
def default_light_color_scheme
ColorSchemeSerializer.new(
ColorScheme.find_by_id(Theme.find_default&.color_scheme_id),
root: false,
).as_json
end
def default_dark_color_scheme
ColorSchemeSerializer.new(
ColorScheme.find_by_id(Theme.find_default&.dark_color_scheme_id),
root: false,
).as_json
end
def groups
cache_anon_fragment("group_names") do
object
.groups
.order(:name)
.select(
:id,
:name,
:flair_icon,
:flair_upload_id,
:flair_bg_color,
:flair_color,
:automatic,
)
.map do |g|
{
id: g.id,
name: g.name,
flair_url: g.flair_url,
flair_bg_color: g.flair_bg_color,
flair_color: g.flair_color,
automatic: g.automatic,
}
end
.as_json
end
end
def post_action_types
Discourse
.cache
.fetch("post_action_types_#{I18n.locale}") do
if PostActionType.overridden_by_plugin_or_skipped_db?
types = ordered_flags(PostActionType.types.values)
ActiveModel::ArraySerializer.new(types).as_json
else
flags = Flag.unscoped.order(:position).where(score_type: false).all
ActiveModel::ArraySerializer.new(
flags,
each_serializer: FlagSerializer,
target: :post_action,
used_flag_ids: self.used_flag_ids(flags.map(&:id)),
).as_json
end
end
end
def topic_flag_types
Discourse
.cache
.fetch("post_action_flag_types_#{I18n.locale}") do
if PostActionType.overridden_by_plugin_or_skipped_db?
types = ordered_flags(PostActionType.topic_flag_types.values)
ActiveModel::ArraySerializer.new(types, each_serializer: TopicFlagTypeSerializer).as_json
else
flags =
Flag
.unscoped
.where("'Topic' = ANY(applies_to)")
.where(score_type: false)
.order(:position)
.all
ActiveModel::ArraySerializer.new(
flags,
each_serializer: FlagSerializer,
target: :topic_flag,
used_flag_ids: self.used_flag_ids(flags.map(&:id)),
).as_json
end
end
end
def default_archetype
Archetype.default
end
def post_types
Post.types
end
def user_tips
User.user_tips
end
def include_user_tips?
SiteSetting.enable_user_tips
end
def filters
Discourse.filters.map(&:to_s)
end
def periods
TopTopic.periods.map(&:to_s)
end
def top_menu_items
Discourse.top_menu_items.map(&:to_s)
end
def anonymous_top_menu_items
Discourse.anonymous_top_menu_items.map(&:to_s)
end
def uncategorized_category_id
SiteSetting.uncategorized_category_id
end
def user_field_max_length
UserField.max_length
end
def can_create_tag
scope.can_create_tag?
end
def can_tag_topics
scope.can_tag_topics?
end
def can_tag_pms
scope.can_tag_pms?
end
def can_associate_groups
scope.can_associate_groups?
end
def include_can_associate_groups?
scope.is_admin?
end
def include_tags_filter_regexp?
SiteSetting.tagging_enabled
end
def tags_filter_regexp
DiscourseTagging::TAGS_FILTER_REGEXP.source
end
def include_top_tags?
Tag.include_tags?
end
def top_tags
@top_tags ||= Tag.top_tags(guardian: scope)
end
def wizard_required
true
end
def include_wizard_required?
Wizard.user_requires_completion?(scope.user)
end
def include_topic_featured_link_allowed_category_ids?
SiteSetting.topic_featured_link_enabled
end
def topic_featured_link_allowed_category_ids
scope.topic_featured_link_allowed_category_ids
end
def censored_regexp
WordWatcher.serialized_regexps_for_action(:censor)
end
def custom_emoji_translation
Plugin::CustomEmoji.translations
end
def shared_drafts_category_id
SiteSetting.shared_drafts_category.to_i
end
def include_shared_drafts_category_id?
scope.can_see_shared_draft? && SiteSetting.shared_drafts_enabled?
end
def watched_words_replace
WordWatcher.regexps_for_action(:replace)
end
def watched_words_link
WordWatcher.regexps_for_action(:link)
end
def categories
object.categories.map { |c| c.to_h }
end
def include_categories?
object.categories.present?
end
def markdown_additional_options
Site.markdown_additional_options
end
def hashtag_configurations
HashtagAutocompleteService.contexts_with_ordered_types
end
def hashtag_icons
HashtagAutocompleteService.data_source_icon_map
end
SIDEBAR_TOP_TAGS_TO_SHOW = 5
def navigation_menu_site_top_tags
if top_tags.present?
top_tag_objects = top_tags[0...SIDEBAR_TOP_TAGS_TO_SHOW]
tag_ids = top_tag_objects.map { |t| t[:id] }
serialized = serialize_tags(Tag.where(id: tag_ids))
# Ensures order of top tags is preserved
serialized.sort_by { |tag| tag_ids.index(tag[:id]) }
else
[]
end
end
def include_navigation_menu_site_top_tags?
SiteSetting.tagging_enabled
end
def anonymous_default_navigation_menu_tags
@anonymous_default_navigation_menu_tags ||=
begin
tag_names =
SiteSetting.default_navigation_menu_tags.split("|") -
DiscourseTagging.hidden_tag_names(scope)
serialize_tags(Tag.where(name: tag_names).order(:name))
end
end
def include_anonymous_default_navigation_menu_tags?
scope.anonymous? && SiteSetting.tagging_enabled &&
SiteSetting.default_navigation_menu_tags.present? &&
anonymous_default_navigation_menu_tags.present?
end
def include_anonymous_sidebar_sections?
scope.anonymous?
end
def whispers_allowed_groups_names
Group.where(id: SiteSetting.whispers_allowed_groups_map).pluck(:name)
end
def include_whispers_allowed_groups_names?
scope.can_see_whispers?
end
def denied_emojis
@denied_emojis ||= Emoji.denied
end
def include_denied_emojis?
denied_emojis.present?
end
def tos_url
Discourse.tos_url
end
def include_tos_url?
tos_url.present?
end
def privacy_policy_url
Discourse.privacy_policy_url
end
def include_privacy_policy_url?
privacy_policy_url.present?
end
def system_user_avatar_template
Discourse.system_user.avatar_template
end
def include_system_user_avatar_template?
SiteSetting.show_user_menu_avatars
end
def lazy_load_categories
true
end
def include_lazy_load_categories?
scope.can_lazy_load_categories?
end
def valid_flag_applies_to_types
Flag.valid_applies_to_types
end
def include_valid_flag_applies_to_types?
scope.is_admin?
end
def admin_config_login_routes
DiscoursePluginRegistry.admin_config_login_routes
end
def include_admin_config_routes?
scope.is_admin?
end
def email_configured
GlobalSetting.smtp_address.present?
end
def full_name_required_for_signup
Site.full_name_required_for_signup
end
def full_name_visible_in_signup
Site.full_name_visible_in_signup
end
private
def ordered_flags(flags)
flags.map { |id| PostActionType.new(id: id) }
end
def used_flag_ids(flag_ids)
@used_flag_ids ||= Flag.used_flag_ids(flag_ids)
end
end