discourse/app/models/badge.rb

281 lines
5.9 KiB
Ruby
Raw Normal View History

require_dependency 'slug'
2014-03-05 07:52:20 -05:00
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
2014-07-07 03:55:25 -04:00
Editor = 10
WikiEditor = 48
FirstLike = 11
FirstShare = 12
FirstFlag = 13
2014-07-09 21:18:02 -04:00
FirstLink = 14
2014-07-11 00:17:01 -04:00
FirstQuote = 15
FirstMention = 40
FirstEmoji = 41
2016-04-12 14:09:59 -04:00
FirstOnebox = 42
FirstReplyByEmail = 43
2014-07-16 02:26:22 -04:00
ReadGuidelines = 16
Reader = 17
NiceTopic = 18
GoodTopic = 19
GreatTopic = 20
NiceShare = 21
GoodShare = 22
GreatShare = 23
Anniversary = 24
2016-03-16 13:48:14 -04:00
Promoter = 25
Campaigner = 26
Champion = 27
2016-03-16 13:48:14 -04:00
PopularLink = 28
HotLink = 29
FamousLink = 30
2016-03-16 13:48:14 -04:00
2016-03-16 13:03:17 -04:00
Appreciated = 36
Respected = 37
Admired = 31
2016-03-16 13:48:14 -04:00
OutOfLove = 33
HigherLove = 34
CrazyInLove = 35
2016-03-16 13:48:14 -04:00
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[*(
2017-07-27 21:20:09 -04:00
Badge::Trigger.constants.map { |k|
[k.to_s.underscore, Badge::Trigger.const_get(k)]
}.flatten
)]
end
2014-07-21 22:46:31 -04:00
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
2014-03-05 07:52:20 -05:00
belongs_to :badge_type
belongs_to :badge_grouping
has_many :user_badges, dependent: :destroy
2014-03-05 07:52:20 -05:00
validates :name, presence: true, uniqueness: true
validates :badge_type, presence: true
validates :allow_title, inclusion: [true, false]
2014-05-23 22:33:46 -04:00
validates :multiple_grant, inclusion: [true, false]
2017-07-27 21:20:09 -04:00
scope :enabled, -> { where(enabled: true) }
before_create :ensure_not_system
Upgrade to FontAwesome 5 (take two) (#6673) * Add missing icons to set * Revert FA5 revert This reverts commit 42572ff * use new SVG syntax in locales * Noscript page changes (remove login button, center "powered by" footer text) * Cast wider net for SVG icons in settings - include any _icon setting for SVG registry (offers better support for plugin settings) - let themes store multiple pipe-delimited icons in a setting - also replaces broken onebox image icon with SVG reference in cooked post processor * interpolate icons in locales * Fix composer whisper icon alignment * Add support for stacked icons * SECURITY: enforce hostname to match discourse hostname This ensures that the hostname rails uses for various helpers always matches the Discourse hostname * load SVG sprite with pre-initializers * FIX: enable caching on SVG sprites * PERF: use JSONP for SVG sprites so they are served from CDN This avoids needing to deal with CORS for loading of the SVG Note, added the svg- prefix to the filename so we can quickly tell in dev tools what the file is * Add missing SVG sprite JSONP script to CSP * Upgrade to FA 5.5.0 * Add support for all FA4.7 icons - adds complete frontend and backend for renamed FA4.7 icons - improves performance of SvgSprite.bundle and SvgSprite.all_icons * Fix group avatar flair preview - adds an endpoint at /svg-sprites/search/:keyword - adds frontend ajax call that pulls icon in avatar flair preview even when it is not in subset * Remove FA 4.7 font files
2018-11-26 16:49:57 -05:00
after_commit do
SvgSprite.expire_cache
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,
2014-09-10 23:30:47 -04:00
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 self.i18n_name(name)
name.downcase.tr(' ', '_')
end
def self.display_name(name)
key = "badges.#{i18n_name(name)}.name"
I18n.t(key, default: name)
end
def awarded_for_trust_level?
id <= 4
end
def reset_grant_count!
self.grant_count = UserBadge.where(badge_id: id).count
save!
end
2014-05-21 03:22:42 -04:00
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
2014-07-29 18:46:46 -04:00
def default_allow_title=(val)
self.allow_title ||= val
end
2014-07-17 02:10:44 -04:00
def default_badge_grouping_id=(val)
2014-07-18 01:55:42 -04:00
# allow to correct orphans
if !self.badge_grouping_id || self.badge_grouping_id <= BadgeGrouping::Other
2014-07-18 01:55:42 -04:00
self.badge_grouping_id = val
end
2014-07-17 02:10:44 -04:00
end
def display_name
self.class.display_name(name)
end
def long_description
key = "badges.#{i18n_name}.long_description"
I18n.t(key, default: self[:long_description] || '', base_uri: Discourse.base_uri)
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_uri)
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
protected
2018-06-07 01:28:18 -04:00
def ensure_not_system
self.id = [Badge.maximum(:id) + 1, 100].max unless id
end
2018-06-07 01:28:18 -04:00
def i18n_name
@i18n_name ||= self.class.i18n_name(name)
2018-06-07 01:28:18 -04:00
end
2014-03-05 07:52:20 -05:00
end
# == Schema Information
#
# Table name: badges
#
2014-07-17 02:10:44 -04:00
# id :integer not null, primary key
2019-01-11 14:29:56 -05:00
# name :string not null
2014-07-17 02:10:44 -04:00
# 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
2014-07-17 02:10:44 -04:00
# allow_title :boolean default(FALSE), not null
# multiple_grant :boolean default(FALSE), not null
2019-01-11 14:29:56 -05:00
# icon :string default("fa-certificate")
2014-07-17 02:10:44 -04:00
# listable :boolean default(TRUE)
# target_posts :boolean default(FALSE)
# query :text
# enabled :boolean default(TRUE), not null
# auto_revoke :boolean default(TRUE), not null
2014-07-21 22:46:31 -04:00
# badge_grouping_id :integer default(5), not null
# trigger :integer
# show_posts :boolean default(FALSE), not null
2014-08-06 23:33:11 -04:00
# system :boolean default(FALSE), not null
# image :string(255)
2015-09-17 20:41:10 -04:00
# long_description :text
2014-03-05 07:52:20 -05:00
#
# Indexes
#
2019-01-11 14:29:56 -05:00
# index_badges_on_badge_type_id (badge_type_id)
# index_badges_on_name (name) UNIQUE
2014-03-05 07:52:20 -05:00
#