discourse/app/models/badge.rb
tshenry a446e0fde1
FEATURE: Link to text customization when editing system badges (#11345)
Being that system badges ship with every instance of Discourse, we've opted to define the name, description, and long description in our locales files to promote translation into other languages. When an admin visited the overview page of a system badge in their admin panel, they were met with disabled inputs for these text properties. The problem is that we failed to educate the admin that the text needs to be managed via the site text customization settings. 

This change adds a small "Customize Text" link under theses inputs that takes the admin to the specific site text customization where they can make desired changes.
2020-12-08 11:55:49 -08:00

334 lines
7.9 KiB
Ruby

# frozen_string_literal: true
class Badge < ActiveRecord::Base
# NOTE: These badge ids are not in order! They are grouped logically.
# When picking an id, *search* for it.
BasicUser = 1
Member = 2
Regular = 3
Leader = 4
Welcome = 5
NicePost = 6
GoodPost = 7
GreatPost = 8
Autobiographer = 9
Editor = 10
WikiEditor = 48
FirstLike = 11
FirstShare = 12
FirstFlag = 13
FirstLink = 14
FirstQuote = 15
FirstMention = 40
FirstEmoji = 41
FirstOnebox = 42
FirstReplyByEmail = 43
ReadGuidelines = 16
Reader = 17
NiceTopic = 18
GoodTopic = 19
GreatTopic = 20
NiceShare = 21
GoodShare = 22
GreatShare = 23
Anniversary = 24
Promoter = 25
Campaigner = 26
Champion = 27
PopularLink = 28
HotLink = 29
FamousLink = 30
Appreciated = 36
Respected = 37
Admired = 31
OutOfLove = 33
HigherLove = 34
CrazyInLove = 35
ThankYou = 38
GivesBack = 32
Empathetic = 39
Enthusiast = 45
Aficionado = 46
Devotee = 47
NewUserOfTheMonth = 44
# other consts
AutobiographerMinBioLength = 10
# used by serializer
attr_accessor :has_badge
def self.trigger_hash
Hash[*(
Badge::Trigger.constants.map { |k|
[k.to_s.underscore, Badge::Trigger.const_get(k)]
}.flatten
)]
end
module Trigger
None = 0
PostAction = 1
PostRevision = 2
TrustLevelChange = 4
UserChange = 8
PostProcessed = 16 # deprecated
def self.is_none?(trigger)
[None].include? trigger
end
def self.uses_user_ids?(trigger)
[TrustLevelChange, UserChange].include? trigger
end
def self.uses_post_ids?(trigger)
[PostAction, PostRevision].include? trigger
end
end
belongs_to :badge_type
belongs_to :badge_grouping
has_many :user_badges, dependent: :destroy
validates :name, presence: true, uniqueness: true
validates :badge_type, presence: true
validates :allow_title, inclusion: [true, false]
validates :multiple_grant, inclusion: [true, false]
scope :enabled, -> { where(enabled: true) }
before_create :ensure_not_system
after_commit do
SvgSprite.expire_cache
UserStat.update_distinct_badge_count if saved_change_to_enabled?
UserBadge.ensure_consistency! if saved_change_to_enabled?
end
# fields that can not be edited on system badges
def self.protected_system_fields
[
:name, :badge_type_id, :multiple_grant,
:target_posts, :show_posts, :query,
:trigger, :auto_revoke, :listable
]
end
def self.trust_level_badge_ids
(1..4).to_a
end
def self.like_badge_counts
@like_badge_counts ||= {
NicePost => 10,
GoodPost => 25,
GreatPost => 50,
NiceTopic => 10,
GoodTopic => 25,
GreatTopic => 50
}
end
def self.ensure_consistency!
DB.exec <<~SQL
DELETE FROM user_badges
USING user_badges ub
LEFT JOIN users u ON u.id = ub.user_id
WHERE u.id IS NULL
AND user_badges.id = ub.id
SQL
DB.exec <<~SQL
WITH X AS (
SELECT badge_id
, COUNT(user_id) users
FROM user_badges
GROUP BY badge_id
)
UPDATE badges
SET grant_count = X.users
FROM X
WHERE id = X.badge_id
AND grant_count <> X.users
SQL
end
def clear_user_titles!
DB.exec(<<~SQL, badge_id: self.id, updated_at: Time.zone.now)
UPDATE users AS u
SET title = '', updated_at = :updated_at
FROM user_profiles AS up
WHERE up.user_id = u.id AND up.granted_title_badge_id = :badge_id
SQL
DB.exec(<<~SQL, badge_id: self.id)
UPDATE user_profiles AS up
SET badge_granted_title = false, granted_title_badge_id = NULL
WHERE up.granted_title_badge_id = :badge_id
SQL
end
##
# Update all user titles based on a badge to the new name
def update_user_titles!(new_title)
DB.exec(<<~SQL, granted_title_badge_id: self.id, title: new_title, updated_at: Time.zone.now)
UPDATE users AS u
SET title = :title, updated_at = :updated_at
FROM user_profiles AS up
WHERE up.user_id = u.id AND up.granted_title_badge_id = :granted_title_badge_id
SQL
end
##
# When a badge has its TranslationOverride cleared, reset
# all user titles granted to the standard name.
def reset_user_titles!
DB.exec(<<~SQL, granted_title_badge_id: self.id, updated_at: Time.zone.now)
UPDATE users AS u
SET title = badges.name, updated_at = :updated_at
FROM user_profiles AS up
INNER JOIN badges ON badges.id = up.granted_title_badge_id
WHERE up.user_id = u.id AND up.granted_title_badge_id = :granted_title_badge_id
SQL
end
def self.i18n_name(name)
name.downcase.tr(' ', '_')
end
def self.display_name(name)
I18n.t(i18n_key(name), default: name)
end
def self.i18n_key(name)
"badges.#{i18n_name(name)}.name"
end
def self.find_system_badge_id_from_translation_key(translation_key)
return unless translation_key.starts_with?('badges.')
badge_name_klass = translation_key.split('.').second.camelize
Badge.const_defined?(badge_name_klass) ? "Badge::#{badge_name_klass}".constantize : nil
end
def awarded_for_trust_level?
id <= 4
end
def reset_grant_count!
self.grant_count = UserBadge.where(badge_id: id).count
save!
end
def single_grant?
!self.multiple_grant?
end
def default_icon=(val)
unless self.image
self.icon ||= val
self.icon = val if self.icon == "fa-certificate"
end
end
def default_allow_title=(val)
self.allow_title ||= val
end
def default_badge_grouping_id=(val)
# allow to correct orphans
if !self.badge_grouping_id || self.badge_grouping_id <= BadgeGrouping::Other
self.badge_grouping_id = val
end
end
def display_name
self.class.display_name(name)
end
def translation_key
self.class.i18n_key(name)
end
def long_description
key = "badges.#{i18n_name}.long_description"
I18n.t(key, default: self[:long_description] || '', base_uri: Discourse.base_path, max_likes_per_day: SiteSetting.max_likes_per_day)
end
def long_description=(val)
self[:long_description] = val if val != long_description
val
end
def description
key = "badges.#{i18n_name}.description"
I18n.t(key, default: self[:description] || '', base_uri: Discourse.base_path, max_likes_per_day: SiteSetting.max_likes_per_day)
end
def description=(val)
self[:description] = val if val != description
val
end
def slug
Slug.for(self.display_name, '-')
end
def manually_grantable?
query.blank? && !system?
end
def i18n_name
@i18n_name ||= self.class.i18n_name(name)
end
protected
def ensure_not_system
self.id = [Badge.maximum(:id) + 1, 100].max unless id
end
end
# == Schema Information
#
# Table name: badges
#
# id :integer not null, primary key
# name :string not null
# description :text
# badge_type_id :integer not null
# grant_count :integer default(0), not null
# created_at :datetime not null
# updated_at :datetime not null
# allow_title :boolean default(FALSE), not null
# multiple_grant :boolean default(FALSE), not null
# icon :string default("fa-certificate")
# listable :boolean default(TRUE)
# target_posts :boolean default(FALSE)
# query :text
# enabled :boolean default(TRUE), not null
# auto_revoke :boolean default(TRUE), not null
# badge_grouping_id :integer default(5), not null
# trigger :integer
# show_posts :boolean default(FALSE), not null
# system :boolean default(FALSE), not null
# image :string(255)
# long_description :text
#
# Indexes
#
# index_badges_on_badge_type_id (badge_type_id)
# index_badges_on_name (name) UNIQUE
#