mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 12:09:27 +08:00
Every endpoint that lists tags for browsing hand-rolled its own filter chain, and they had drifted apart. The tags page filtered synonyms out when tags were listed flat but not when they were listed by group, so a synonym of a tag belonging to no tag group was shown next to its target — a duplicate entry whose page only redirects back to the tag it duplicates. Synonyms of grouped tags were hidden by accident rather than by design: creating a synonym copies the target's tag group memberships onto it, which happens to satisfy the "belongs to no tag group" condition the ungrouped list is built from. Non-admins were shielded by a second accident, since a synonym normally ends up with a zero topic count — so any synonym that kept its topics, whether from an import, a plugin, or assigning `target_tag_id` directly, was listed to everyone. Tag visibility has two independent mechanisms: tag group permissions, and the categories a tag is attached to. Only the second one can apply to a tag that belongs to no tag group, and neither the grouped tags page nor the tag group search endpoint applied it, so the names of tags restricted to a category the viewer cannot read were listed to them. `TagGroup.visible` gates which groups are returned, never the tags inside them. The navigation menu tag picker filtered out neither synonyms nor tags only used in personal messages, and gated on staff where the tags page gates on admin, so moderators were served the entire tag table. A synonym picked there was saved as a sidebar link and rendered from then on. Top tags and tag search had the same gap. Paths that resolve a tag by name keep matching synonyms on purpose: the composer offers them so that typing a retired name finds the tag that replaced it, and hashtags in posts cooked before a rename have to keep working. Serializers that echo configuration back are left alone, because filtering a value the client posts straight back would silently delete it.
82 lines
2.3 KiB
Ruby
Vendored
82 lines
2.3 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
class FormTemplate < ActiveRecord::Base
|
|
validates :name,
|
|
presence: true,
|
|
uniqueness: true,
|
|
length: {
|
|
maximum: -> { SiteSetting.max_form_template_title_length },
|
|
}
|
|
validates :template,
|
|
presence: true,
|
|
length: {
|
|
maximum: -> { SiteSetting.max_form_template_content_length },
|
|
}
|
|
validates_with FormTemplateYamlValidator, if: ->(ft) { ft.template }
|
|
|
|
has_many :category_form_templates, dependent: :destroy
|
|
has_many :categories, through: :category_form_templates
|
|
|
|
class NotAllowed < StandardError
|
|
end
|
|
|
|
def process!(guardian)
|
|
parsed_template = YAML.safe_load(template)
|
|
|
|
tag_group_names = parsed_template.map { |f| f["tag_group"] }.compact.map(&:downcase).uniq
|
|
|
|
tag_groups =
|
|
TagGroup
|
|
.includes(:base_tags)
|
|
.visible(guardian)
|
|
.where("lower(name) IN (?)", tag_group_names)
|
|
.index_by { |tg| tg.name }
|
|
|
|
parsed_template.map! do |form_field|
|
|
next form_field unless form_field["tag_group"]
|
|
|
|
tag_group_name = form_field["tag_group"]
|
|
tags = tag_groups[tag_group_name].base_tags
|
|
ordered_field = {}
|
|
|
|
tags =
|
|
tags.sort_by do |t|
|
|
# Transform the name to the displayed value before ordering
|
|
display = t.description.presence || t.name.tr("-", " ").upcase
|
|
display
|
|
end
|
|
|
|
form_field.each do |key, value|
|
|
ordered_field[key] = value
|
|
|
|
ordered_field["choices"] = tags.map { |t| { "id" => t.id, "name" => t.name } } if key ==
|
|
"id"
|
|
end
|
|
|
|
ordered_field["attributes"] ||= {}
|
|
ordered_field["attributes"]["tag_group"] = tag_group_name
|
|
translated_tags = tags.select { |t| t.description }.to_h { |t| [t.name, t.description] }
|
|
ordered_field["attributes"]["tag_choices"] = translated_tags
|
|
|
|
ordered_field
|
|
end
|
|
|
|
self.template = YAML.dump(parsed_template)
|
|
self
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: form_templates
|
|
#
|
|
# id :bigint not null, primary key
|
|
# name :string not null
|
|
# template :text not null
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_form_templates_on_name (name) UNIQUE
|
|
#
|