2016-05-04 13:02:47 -05:00
|
|
|
class Tag < ActiveRecord::Base
|
2017-08-25 10:52:18 -05:00
|
|
|
include Searchable
|
2018-10-05 03:53:59 -05:00
|
|
|
include HasDestroyedWebHook
|
2017-08-25 10:52:18 -05:00
|
|
|
|
2018-10-05 04:23:52 -05:00
|
|
|
validates :name, presence: true, uniqueness: { case_sensitive: false }
|
|
|
|
|
|
|
|
scope :where_name, ->(name) do
|
|
|
|
name = Array(name).map(&:downcase)
|
|
|
|
where("lower(name) IN (?)", name)
|
|
|
|
end
|
2016-05-30 15:37:06 -05:00
|
|
|
|
2018-11-12 10:24:34 -06:00
|
|
|
scope :unused, -> { where(topic_count: 0, pm_topic_count: 0) }
|
|
|
|
|
2016-05-30 15:37:06 -05:00
|
|
|
has_many :tag_users # notification settings
|
|
|
|
|
2016-05-04 13:02:47 -05:00
|
|
|
has_many :topic_tags, dependent: :destroy
|
|
|
|
has_many :topics, through: :topic_tags
|
2016-05-30 15:37:06 -05:00
|
|
|
|
|
|
|
has_many :category_tags, dependent: :destroy
|
|
|
|
has_many :categories, through: :category_tags
|
2016-05-04 13:02:47 -05:00
|
|
|
|
2016-06-06 13:18:15 -05:00
|
|
|
has_many :tag_group_memberships
|
|
|
|
has_many :tag_groups, through: :tag_group_memberships
|
|
|
|
|
2017-08-25 10:52:18 -05:00
|
|
|
after_save :index_search
|
|
|
|
|
2018-04-04 07:19:20 -05:00
|
|
|
after_commit :trigger_tag_created_event, on: :create
|
2018-05-21 04:29:19 -05:00
|
|
|
after_commit :trigger_tag_updated_event, on: :update
|
2018-04-04 07:19:20 -05:00
|
|
|
after_commit :trigger_tag_destroyed_event, on: :destroy
|
|
|
|
|
2018-01-11 10:59:51 -06:00
|
|
|
def self.ensure_consistency!
|
2018-05-14 12:02:00 -05:00
|
|
|
update_topic_counts
|
2018-01-11 10:59:51 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.update_topic_counts
|
2018-06-19 01:13:14 -05:00
|
|
|
DB.exec <<~SQL
|
2018-01-11 10:59:51 -06:00
|
|
|
UPDATE tags t
|
2018-05-14 12:02:00 -05:00
|
|
|
SET topic_count = x.topic_count
|
|
|
|
FROM (
|
|
|
|
SELECT COUNT(topics.id) AS topic_count, tags.id AS tag_id
|
|
|
|
FROM tags
|
|
|
|
LEFT JOIN topic_tags ON tags.id = topic_tags.tag_id
|
|
|
|
LEFT JOIN topics ON topics.id = topic_tags.topic_id
|
|
|
|
AND topics.deleted_at IS NULL
|
|
|
|
AND topics.archetype != 'private_message'
|
|
|
|
GROUP BY tags.id
|
|
|
|
) x
|
|
|
|
WHERE x.tag_id = t.id
|
|
|
|
AND x.topic_count <> t.topic_count
|
|
|
|
SQL
|
|
|
|
|
2018-06-19 01:13:14 -05:00
|
|
|
DB.exec <<~SQL
|
2018-05-14 12:02:00 -05:00
|
|
|
UPDATE tags t
|
|
|
|
SET pm_topic_count = x.pm_topic_count
|
|
|
|
FROM (
|
|
|
|
SELECT COUNT(topics.id) AS pm_topic_count, tags.id AS tag_id
|
|
|
|
FROM tags
|
|
|
|
LEFT JOIN topic_tags ON tags.id = topic_tags.tag_id
|
|
|
|
LEFT JOIN topics ON topics.id = topic_tags.topic_id
|
|
|
|
AND topics.deleted_at IS NULL
|
|
|
|
AND topics.archetype = 'private_message'
|
|
|
|
GROUP BY tags.id
|
|
|
|
) x
|
|
|
|
WHERE x.tag_id = t.id
|
|
|
|
AND x.pm_topic_count <> t.pm_topic_count
|
2018-01-11 10:59:51 -06:00
|
|
|
SQL
|
2016-05-31 15:27:13 -05:00
|
|
|
end
|
|
|
|
|
2018-10-05 04:23:52 -05:00
|
|
|
def self.find_by_name(name)
|
|
|
|
self.find_by('lower(name) = ?', name.downcase)
|
|
|
|
end
|
|
|
|
|
2016-09-22 14:23:10 -05:00
|
|
|
def self.top_tags(limit_arg: nil, category: nil, guardian: nil)
|
2016-07-07 08:17:56 -05:00
|
|
|
limit = limit_arg || SiteSetting.max_tags_in_filter_list
|
2017-07-27 20:20:09 -05:00
|
|
|
scope_category_ids = (guardian || Guardian.new).allowed_category_ids
|
2016-07-07 08:17:56 -05:00
|
|
|
|
2016-09-22 14:23:10 -05:00
|
|
|
if category
|
|
|
|
scope_category_ids &= ([category.id] + category.subcategories.pluck(:id))
|
|
|
|
end
|
|
|
|
|
2018-02-08 15:36:39 -06:00
|
|
|
return [] if scope_category_ids.empty?
|
|
|
|
|
2018-04-20 14:25:28 -05:00
|
|
|
filter_sql = guardian&.is_staff? ? '' : " AND tags.id NOT IN (#{DiscourseTagging.hidden_tags_query.select(:id).to_sql})"
|
2018-03-26 16:04:55 -05:00
|
|
|
|
2018-06-19 01:13:14 -05:00
|
|
|
tag_names_with_counts = DB.query <<~SQL
|
2018-02-08 15:36:39 -06:00
|
|
|
SELECT tags.name as tag_name, SUM(stats.topic_count) AS sum_topic_count
|
|
|
|
FROM category_tag_stats stats
|
2018-05-14 12:02:00 -05:00
|
|
|
JOIN tags ON stats.tag_id = tags.id AND stats.topic_count > 0
|
2018-02-08 15:36:39 -06:00
|
|
|
WHERE stats.category_id in (#{scope_category_ids.join(',')})
|
2018-03-26 16:04:55 -05:00
|
|
|
#{filter_sql}
|
2018-02-08 15:36:39 -06:00
|
|
|
GROUP BY tags.name
|
|
|
|
ORDER BY sum_topic_count DESC, tag_name ASC
|
|
|
|
LIMIT #{limit}
|
|
|
|
SQL
|
2016-07-07 08:17:56 -05:00
|
|
|
|
2018-06-19 01:13:14 -05:00
|
|
|
tag_names_with_counts.map { |row| row.tag_name }
|
2016-07-07 08:17:56 -05:00
|
|
|
end
|
|
|
|
|
2018-03-15 07:33:08 -05:00
|
|
|
def self.pm_tags(limit_arg: nil, guardian: nil, allowed_user: nil)
|
|
|
|
return [] if allowed_user.blank? || !(guardian || Guardian.new).can_tag_pms?
|
2018-02-27 10:51:40 -06:00
|
|
|
limit = limit_arg || SiteSetting.max_tags_in_filter_list
|
2018-03-15 07:33:08 -05:00
|
|
|
user_id = allowed_user.id
|
2018-02-27 10:51:40 -06:00
|
|
|
|
2018-06-19 01:13:14 -05:00
|
|
|
DB.query_hash(<<~SQL).map!(&:symbolize_keys!)
|
|
|
|
SELECT tags.name as id, tags.name as text, COUNT(topics.id) AS count
|
2018-05-14 12:02:00 -05:00
|
|
|
FROM tags
|
|
|
|
JOIN topic_tags ON tags.id = topic_tags.tag_id
|
|
|
|
JOIN topics ON topics.id = topic_tags.topic_id
|
|
|
|
AND topics.deleted_at IS NULL
|
|
|
|
AND topics.archetype = 'private_message'
|
|
|
|
WHERE topic_tags.topic_id IN (
|
|
|
|
SELECT topic_id
|
|
|
|
FROM topic_allowed_users
|
|
|
|
WHERE user_id = #{user_id}
|
|
|
|
UNION
|
|
|
|
SELECT tg.topic_id
|
|
|
|
FROM topic_allowed_groups tg
|
|
|
|
JOIN group_users gu ON gu.user_id = #{user_id}
|
|
|
|
AND gu.group_id = tg.group_id
|
|
|
|
)
|
|
|
|
GROUP BY tags.name
|
|
|
|
LIMIT #{limit}
|
2018-02-27 10:51:40 -06:00
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2016-07-07 08:17:56 -05:00
|
|
|
def self.include_tags?
|
|
|
|
SiteSetting.tagging_enabled && SiteSetting.show_filter_by_tag
|
2016-05-04 13:02:47 -05:00
|
|
|
end
|
2016-08-01 21:54:32 -05:00
|
|
|
|
|
|
|
def full_url
|
|
|
|
"#{Discourse.base_url}/tags/#{self.name}"
|
|
|
|
end
|
2017-08-25 10:52:18 -05:00
|
|
|
|
|
|
|
def index_search
|
|
|
|
SearchIndexer.index(self)
|
|
|
|
end
|
2018-04-04 07:19:20 -05:00
|
|
|
|
2018-05-21 04:29:19 -05:00
|
|
|
%i{
|
|
|
|
tag_created
|
|
|
|
tag_updated
|
|
|
|
tag_destroyed
|
|
|
|
}.each do |event|
|
|
|
|
define_method("trigger_#{event}_event") do
|
|
|
|
DiscourseEvent.trigger(event, self)
|
|
|
|
true
|
|
|
|
end
|
2018-04-04 07:19:20 -05:00
|
|
|
end
|
2016-05-04 13:02:47 -05:00
|
|
|
end
|
2016-05-29 19:45:32 -05:00
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: tags
|
|
|
|
#
|
2018-05-14 12:02:00 -05:00
|
|
|
# id :integer not null, primary key
|
|
|
|
# name :string not null
|
|
|
|
# topic_count :integer default(0), not null
|
2019-01-11 11:19:23 -06:00
|
|
|
# created_at :datetime
|
|
|
|
# updated_at :datetime
|
2018-05-14 12:02:00 -05:00
|
|
|
# pm_topic_count :integer default(0), not null
|
2016-05-29 19:45:32 -05:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
2018-10-08 09:40:05 -05:00
|
|
|
# index_tags_on_lower_name (lower((name)::text)) UNIQUE
|
|
|
|
# index_tags_on_name (name) UNIQUE
|
2016-05-29 19:45:32 -05:00
|
|
|
#
|